Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Netty4ClientHttpRequestFactory should configure a default SslContext [SPR-14744] #19310

Closed
spring-projects-issues opened this issue Sep 23, 2016 · 4 comments
Assignees
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

@spring-projects-issues spring-projects-issues commented Sep 23, 2016

Joaquim Pedro Silveira opened SPR-14744 and commented

As title describes, when trying to use AsyncRestTemplate with a Netty4ClientHttpRequestFactory, the client is not properly configured by default for https servers.

I also tried with the following code, which results in an HttpClientErrorException:

@Test
public void asyncRestNetty() throws Exception {
    final CountDownLatch latch = new CountDownLatch(1);
    AsyncRestTemplate restTemplate = new AsyncRestTemplate(new Netty4ClientHttpRequestFactory());
    // the client should send a request to an HTTPS server
    ListenableFuture<ResponseEntity<String>> listenableFuture = restTemplate.getForEntity("https://example.org/some/resource", String.class);
    listenableFuture.addCallback((result) -> {
        latch.countDown();
        assertThat(result, is(notNullValue()));
        assertThat(result.getBody(), is(not(isEmptyOrNullString())));
    }, (throwable) -> {
        latch.countDown();
        fail(throwable.getMessage());
    });
    if (!latch.await(30, TimeUnit.SECONDS)) {
        fail("Timeout");
    }
}

Affects: 4.3.3

Reference URL: http://stackoverflow.com/questions/39639467/spring-resttemplate-asyncresttemplate-with-netty4-hangs-forever

Issue Links:

  • #19426 RestTemplate POST and PUT don't work with Netty4ClientHttpRequestFactory
  • #19455 Netty4ClientHttpRequestFactory should use SSL if port is 443 or scheme is https
  • #19668 Netty4ClientHttpRequestFactory should use SNI for TLS connections
@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Sep 26, 2016

Brian Clozel commented

This code is working for a any http URL, but I suspect you're trying to contact an https endpoint.

In that case, can you try to configure your request factory with a Netty Sslcontext?

Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory();
nettyFactory.setSslContext(SslContextBuilder.forClient().build());
AsyncRestTemplate restTemplate = new AsyncRestTemplate(nettyFactory);

Without that context, the client is trying to send plaintext requests to the https endpoint; in that case, you're probably getting an HTTP 400 response.

In your example code, the throwable should be an instance of HttpClientErrorException, and you could get that information by logging the response status or its body with exception.getResponseBodyAsString().

Let me know if this configuration works out and we can discuss ways to improve the current situation.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Sep 26, 2016

Joaquim Pedro Silveira commented

Indeed I was trying to reach a SSL URL, this one to be precise:
URL = "https://api.github.com/users/octocat";.

Adding the SslContext solved the hanging issue and for this specific URL I also had to add a USER_AGENT header.

My only suggestion is, if possible, if missing this SSL setting for Netty when trying to reach https endpoints should somehow throw an exception instead of just hanging the application. Because in my initial setup the failureCallback was never invoked which is odd. Maybe this is an Netty issue? I say this because when using plain AsyncRestTemplate or with OkHttp3ClientHttpRequestFactory the SSL config was included without any action on my part.

What do you think?

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Sep 26, 2016

Brian Clozel commented

If you don't mind, I'll edit this ticket to make it an improvement and register automatically a default SslContext with the client.

Now I've tested this and the failure callback is really called with a HttpClientErrorException.
It's just that we're testing asynchronous code and two things can happen in your example code:

  • if the latch countdown happens before the asserts, then the test can exit before the asserts happen
  • if the asserts happen before the latch countdown, those failing asserts result in throwing Exceptions from a different thread, and those exceptions aren't caught by the junit thread; since the latch countdown is never called, it hangs
@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Sep 26, 2016

Joaquim Pedro Silveira commented

Ok I could narrow down the problem a bit more.

This works as expected: (where ExecutionException is cause by HttpClientErrorException)

@Test(expected = ExecutionException.class)
public void expectedExceptionNetty() throws Exception {
    AsyncRestTemplate restTemplate = new AsyncRestTemplate(new Netty4ClientHttpRequestFactory());
    ListenableFuture<ResponseEntity<String>> listenableFuture = restTemplate.getForEntity("https://jsonplaceholder.typicode.com/posts/1", String.class);
    ResponseEntity<String> response = listenableFuture.get();
    assertThat(response, is(notNullValue()));
}

But this one hangs:

@Test
public void hangingNetty() throws Exception {
    AsyncRestTemplate restTemplate = new AsyncRestTemplate(new Netty4ClientHttpRequestFactory());
    ListenableFuture<ResponseEntity<String>> listenableFuture = restTemplate.getForEntity("https://api.github.com/users/octocat", String.class);
    ResponseEntity<String> response = listenableFuture.get();
    assertThat(response, is(notNullValue()));
}

So it is something related to how GitHub server replies when no certificates are available and Netty gets lost?

I also did this curl to see what happens and seems like Netty does not handle empty replies from server like curl:

curl "http://api.github.com:443/users/octocat" -v
*   Trying 192.30.253.116...
* Connected to api.github.com (192.30.253.116) port 443 (#0)
> GET /users/octocat HTTP/1.1
> Host: api.github.com:443
> User-Agent: curl/7.43.0
> Accept: */*
> 
* Empty reply from server
* Connection #0 to host api.github.com left intact
curl: (52) Empty reply from server
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants