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

HTTP/2 not properly supported for PUT requests #2637

Open
wimdeblauwe opened this issue Mar 13, 2024 · 10 comments
Open

HTTP/2 not properly supported for PUT requests #2637

wimdeblauwe opened this issue Mar 13, 2024 · 10 comments
Labels

Comments

@wimdeblauwe
Copy link

Proposal

When writing a wiremock test that tests a PUT request via Spring's RestClient that uses the JDK 11 HttpClient as the underlying client, I get java.io.EOFException: EOF reached while reading error.

By explicitly configuring to use HTTP 1.1 instead of HTTP/2, the error no longer happens.

See https://stackoverflow.com/questions/78151993/eof-reached-while-reading-with-spring-restclient-and-wiremock-using-jdkclienth for my SO post about the issue.

Reproduction steps

There is a reproducer project at https://github.com/wimdeblauwe/restclient-jdk-issue

References

No response

@nut-3
Copy link

nut-3 commented Mar 17, 2024

If I'm not mistaken there are no implementations of HTTP/2 without TLS. So my guess is that you set up a http stub that is served with HTTP/1.1. So it might be Java HttpClient's bug for not downgrading to HTTP/1.1 on non-TLS connection or not handling failed upgrade attempt properly. Or maybe underlying Jetty is not configured to use h2c for not-TLS HTTP/2 connections (probably need some configuration to enable it).
Actually, your code can be changed to use only one ClientHttpRequestFactory:

        return RestClient.builder()
                .requestFactory(new JdkClientHttpRequestFactory(HttpClient.newBuilder()
                        .version(HttpClient.Version.HTTP_1_1)
                        .build()))
                .baseUrl(properties.baseUrl())
                .build();

and it works.

@wimdeblauwe
Copy link
Author

The point is that Wiremock is that HTTP stub and forcing HTTP/1.1 should not be needed.

@nut-3
Copy link

nut-3 commented Mar 17, 2024

Well, I can only add that I could not reproduce error by using plain Jdk HttpClient like this:

        var httpClient = HttpClient.newHttpClient();
        HttpResponse<String> response = httpClient.send(HttpRequest.newBuilder()
                        .uri(new URI(runtimeInfo.getHttpBaseUrl() + "/something"))
                        .header("Content-Type", "application/json")
                        .PUT(HttpRequest.BodyPublishers.ofString("{ \"data\": \"test\" }"))
                        .build(),
                HttpResponse.BodyHandlers.ofString()
        );

Same using sendAsync(). Maybe error has something to do with Spring RestClient usage of HttpClient.

@mdeinum
Copy link

mdeinum commented Mar 18, 2024

The Spring RestClient is nothing more then a wrapper, however internally it will use a Streaming Request and not a plain String or other simple type.

Rewriting the code to use something streaming makes it fail with a plain HttpClient as well.

var client = HttpClient.newHttpClient();
var body = new ObjectMapper().writeValueAsBytes(new SomeRequest(16));
var publisher = HttpRequest.BodyPublishers.ofByteArray(body, 0, body.length);
var request = HttpRequest.newBuilder().
  .uri(URI.create(runtimeInfo.getHttpBaseUrl() + "/something"))
  .header("Content-Type", "application/json")
  .PUT(HttpRequest.BodyPublishers.fromPublisher(publisher))
  .build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());

Result: java.io.IOException: EOF reached while reading

I've attached the logging for the HttpClient maybe that can shed some light. The output for both the RestClient or plain HttpClient is the same as well as the end result.

logging-when-eof.txt

UPDATE: Fails with a simple byte[] as well in the exact same manner.

@mdeinum
Copy link

mdeinum commented Mar 19, 2024

#2461

This issue seems to discuss/elaborate the issue. Apparently HTTP/2 with an InputStream body is only supported on HTTPS.

@uncl01
Copy link

uncl01 commented Mar 25, 2024

Same issue here #2585, the reason it can be hard to reproduce is that it is also linked to the size of the request.

@tomakehurst
Copy link
Member

From a quick tcpdump session, as far as I can tell after WireMock has sent the 101 Upgrade response to HTTP/2 the client starts sending encrypted data despite us still being on a plain text connection.

The reproducer test passes if you set the app's base URL to use HTTPS and disable certificate validation.

I'm not sure there's much we can do here apart from add the option to disable HTTP/2 so that no upgrade happens.

@tomakehurst
Copy link
Member

On further inspection, this isn't encryption related but definitely seems to be triggered by the combination of upgrade + PUT + chunked encoded request body.

Seems this is undefined behaviour as far as Jetty is concerned unfortunately:
jetty/jetty.project#3235

@tomakehurst
Copy link
Member

HTTP/2 toggles added: 363e2ff

@tomakehurst
Copy link
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants