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

[spring-cloud-gateway-mvc] GET requests fail on HTTP/2 upgrade #3141

Open
ruedigerschlegel-cpi opened this issue Nov 17, 2023 · 1 comment
Open

Comments

@ruedigerschlegel-cpi
Copy link

ruedigerschlegel-cpi commented Nov 17, 2023

Boot Version: 3.2.0-M3
Gateway Version: 4.1.0-RC1

We recently started with a new application and decided to use spring-cloud-gateway-mvc as the basis. When writing tests "around" our application with WireMock standing in for the target service we noticed that very simple GET requests were not processed successfully but failed with this error message:

java.io.EOFException: EOF reached while reading
	at java.net.http/jdk.internal.net.http.Http2Connection$Http2TubeSubscriber.onComplete(Http2Connection.java:1587) ~[java.net.http:na]
	at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$ReadSubscription.signalCompletion(SocketTube.java:648) ~[java.net.http:na]
	at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.read(SocketTube.java:853) ~[java.net.http:na]
	at java.net.http/jdk.internal.net.http.SocketTube$SocketFlowTask.run(SocketTube.java:181) ~[java.net.http:na]
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:207) ~[java.net.http:na]
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:280) ~[java.net.http:na]
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:233) ~[java.net.http:na]
	at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.signalReadable(SocketTube.java:782) ~[java.net.http:na]
	at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$ReadEvent.signalEvent(SocketTube.java:965) ~[java.net.http:na]
	at java.net.http/jdk.internal.net.http.SocketTube$SocketFlowEvent.handle(SocketTube.java:253) ~[java.net.http:na]
	at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.handleEvent(HttpClientImpl.java:1467) ~[java.net.http:na]
	at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.lambda$run$3(HttpClientImpl.java:1412) ~[java.net.http:na]
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) ~[na:na]
	at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:1412) ~[java.net.http:na]

This only happens when WireMock is called through the gateway, attempts to reproduce the error with several HTTP client implementations calling WireMock directly were unsuccessful. Putting NGINX between the gateway and WireMock prevented the HTTP/2 upgrade and the request worked as expected. After some digging we found out that the JDK HTTP client used by Spring will send the GET request as stream with a Transfer-encoding: chunked header. This happens because the content-length is reported as -1 to the HTTP client as the request body that is passed to the bodyPublisher methond in HttpRequest.BodyPublisher is empty but not null.

I'm aware of the fact that the mentioned classes are residing in the spring-web part of the spring-framework project, but since it is no longer possible at this point to check for an empty body this will probably have to be fixed at an earlier stage.

What seems to trigger this error is that the server (Jetty in this case) initiates the upgrade and waits for the client to send the preface. Instead of doing so the client appears to send an empty chunk to indicate that it is done which then causes the server to send an ìnvalid_preface error and terminate the connection.

Sample
I added a small project that can be used to reproduce the issue. It just defines one route that passes everything to WireMock and contains tests that include a call to WireMock through the gateway (fails) and directly using the JDK client (succeeds). I would expect both tests to work once a fix has been applied.
http2-upgrade-demo.zip

@spencergibb
Copy link
Member

Setting the following as a workaround switches the http client and all the tests pass

spring.cloud.gateway.mvc.http-client.type=autodetect

The wiremock test dependency brings in org.apache.httpcomponents.client5:httpclient5 which is what us used in the successful tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Todo
Development

No branches or pull requests

2 participants