Quarkus: Simple HTTP request fail with server error 500, when using JDK 11 HTTP client and websocket installed

Created on 20 May 2020  Â·  11Comments  Â·  Source: quarkusio/quarkus

Describe the bug

Simple HTTP request fail with server error 500, when using JDK 11 HTTP client, and the undertow-websockets extension is installed.

Expected behavior

This should 200, as when the extension is not present.

Actual behavior

Sending an HTTP request using:

  • the JDK 11 HTTP client (I'm using OpenJDK Runtime Environment GraalVM CE 19.3.1 (build 11.0.6+9-jvmci-19.3-b07))
  • with undertow-websockets extension installed

The response is 500, the stacktrace in the logs is:

May 20, 2020 10:02:59 AM io.quarkus.vertx.http.runtime.QuarkusErrorHandler handle
ERROR: HTTP Request to / failed, error id: 0f8b1890-525a-4d59-b1c3-aae24aeb6c61-1
java.util.NoSuchElementException: websocketExtensionHandler
    at io.netty.channel.DefaultChannelPipeline.getContextOrDie(DefaultChannelPipeline.java:1073)
    at io.netty.channel.DefaultChannelPipeline.remove(DefaultChannelPipeline.java:423)
    at io.undertow.vertx.VertxHttpExchange.<init>(VertxHttpExchange.java:203)
    at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$6.handle(UndertowDeploymentRecorder.java:380)
    at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$6.handle(UndertowDeploymentRecorder.java:373)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1034)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:131)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:130)
    at io.vertx.ext.web.impl.RouterImpl.handle(RouterImpl.java:54)
    at io.vertx.ext.web.impl.RouterImpl.handle(RouterImpl.java:36)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$7.handle(VertxHttpRecorder.java:334)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$7.handle(VertxHttpRecorder.java:331)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$1.handle(VertxHttpRecorder.java:112)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$1.handle(VertxHttpRecorder.java:103)
    at io.vertx.core.impl.ContextImpl.executeTask(ContextImpl.java:369)
    at io.vertx.core.impl.EventLoopContext.execute(EventLoopContext.java:43)
    at io.vertx.core.impl.ContextImpl.executeFromIO(ContextImpl.java:232)
    at io.vertx.core.http.impl.Http2ServerConnection.onHeadersRead(Http2ServerConnection.java:136)
    at io.vertx.core.http.impl.Http2ServerConnection.onHeadersRead(Http2ServerConnection.java:149)
    at io.vertx.core.http.impl.VertxHttp2ConnectionHandler.channelRead(VertxHttp2ConnectionHandler.java:411)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
    at io.vertx.core.http.impl.Http1xUpgradeToH2CHandler.channelRead(Http1xUpgradeToH2CHandler.java:95)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
    at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:321)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:295)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
    at io.vertx.core.http.impl.Http1xOrH2CHandler.end(Http1xOrH2CHandler.java:61)
    at io.vertx.core.http.impl.Http1xOrH2CHandler.channelRead(Http1xOrH2CHandler.java:38)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
    at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:834)

To Reproduce

I've actually created a repository that has a simple test reproducing the issue, https://bitbucket.org/sleberrigaud/quarkus-websocket-jdk-httpclient-bug.

Out of the box, the GreetingResourceWithJdkHttpClientTest shows the issue. Remove the undertow-websockets extension and the test passes fine.

Note that trying the same request via the browser (Chrome or Firefox) works fine. The GreetingResourceTest is also fine. I've also tried the OpenFeign client, and it works as well.

I don't understand what the JDK HTTP client does so special that it triggers the 500. And while there might be a bug in the client itself, it shouldn't trigger a 500 on Quarkus' side, as the worst I would have expected a 4xx because my request was not properly formed.

kinbug

All 11 comments

I see io.vertx.core.http.impl.Http2ServerConnection in the stacktrace.

Is the HTTP Client initiating an HTTP2 connection for some reason?

It appears to be because of this https://github.com/quarkusio/quarkus-http/pull/29

@geoand well spotted, it could be that the HTTP client tries HTTP/2 by default. I had no idea TBH. That's actually what it says here: https://openjdk.java.net/groups/net/httpclient/intro.html

The Java HTTP Client supports both HTTP/1.1 and HTTP/2. By default the client will send requests using HTTP/2. Requests sent to servers that do not yet support HTTP/2 will automatically be downgraded to HTTP/1.1.

@sleberrigaud , hi, I downloaded your code and did a test on my environment, and everything was ok .... you could consider updating the version of GraalVM. Or use https://adoptopenjdk.net (OpenJdk V11, JVM OpenJ9).

carlossantos@Carloss-MacBook-Pro quarkus-websocket-jdk-httpclient-bug % java -version
openjdk version "11.0.6" 2020-01-14
OpenJDK Runtime Environment GraalVM CE 20.0.0 (build 11.0.6+9-jvmci-20.0-b02)
OpenJDK 64-Bit Server VM GraalVM CE 20.0.0 (build 11.0.6+9-jvmci-20.0-b02, mixed mode, sharing)
carlossantos@Carloss-MacBook-Pro quarkus-websocket-jdk-httpclient-bug % ./mvnw quarkus:dev
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< org.acme:getting-started >----------------------
[INFO] Building getting-started 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- quarkus-maven-plugin:1.4.2.Final:dev (default-cli) @ getting-started ---
[INFO] Nothing to compile - all classes are up to date
OpenJDK 64-Bit Server VM warning: forcing TieredStopAtLevel to full optimization because JVMCI is enabled
Listening for transport dt_socket at address: 5005
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--________/_/ |_/_/|_/_/|_|____/___/
2020-05-20 10:18:55,710 INFO [io.quarkus] (Quarkus Main Thread) getting-started 1.0-SNAPSHOT (powered by Quarkus 1.4.2.Final) started in 1.473s. Listening on: http://0.0.0.0:8080
2020-05-20 10:18:55,735 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2020-05-20 10:18:55,735 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy, servlet, undertow-websockets]

@jaikiran now that your quarkus-http PR has been merged, I assume all we need is new release to be picked up by Quarkus to fix this?

Hello @geoand, although I haven't personally tried this application against a snapshot version containing that fix, I do believe that the fix should get the user past this specific exception, once it's integrated into Quarkus.

@carlosepdsJava so I've just tried with both different JDK and I get the same issue. For reference:

openjdk 11.0.5 2019-10-15
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.5+10)
Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.17.0, JRE 11 Mac OS X amd64-64-Bit Compressed References 20191016_371 (JIT enabled, AOT enabled)
OpenJ9   - 77c1cf708
OMR      - 20db4fbc
JCL      - 2a7af5674b based on jdk-11.0.5+10)

and

openjdk 11.0.7 2020-04-14
OpenJDK Runtime Environment GraalVM CE 20.1.0 (build 11.0.7+10-jvmci-20.1-b02)
OpenJDK 64-Bit Server VM GraalVM CE 20.1.0 (build 11.0.7+10-jvmci-20.1-b02, mixed mode, sharing)

Note that it's not bringing up the Quarkus app that's the issue, but actually making any HTTP request to the application, so to reproduce the issue, in my example project, running mvn test fails… when it shouldn't.

@sleberrigaud, this is a genuine bug and you will need the fix that got merged in https://github.com/quarkusio/quarkus-http/pull/29

It seems it is using HTTP/2 and currently HTTP/2 does not support WebSocket. WebSocket has to be initiated using HTTP/1 . There is currently this RFC for supporting WebSocket with HTTP/2 https://tools.ietf.org/html/rfc8441 . It seems that Netty does not implement it yet https://github.com/netty/netty/issues/8011

@sleberrigaud, in the meantime to get past this, you can change your test case as follows and get it working:

diff --git a/src/test/java/org/acme/getting/started/GreetingResourceWithJdkHttpClientTest.java b/src/test/java/org/acme/getting/started/GreetingResourceWithJdkHttpClientTest.java
index c68fadd..44667a4 100644
--- a/src/test/java/org/acme/getting/started/GreetingResourceWithJdkHttpClientTest.java
+++ b/src/test/java/org/acme/getting/started/GreetingResourceWithJdkHttpClientTest.java
@@ -21,7 +21,7 @@ public class GreetingResourceWithJdkHttpClientTest {
                 .GET()
                 .build();

-        final var status = HttpClient.newHttpClient()
+        final var status = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build()
                 .sendAsync(request, BodyHandlers.ofString())
                 .thenApply(HttpResponse::statusCode)
                 .join();

I'm going to close this since 1.5.0 of Quarkus, contains the 3.0.9 version of quarkus-http which has the fix for the above exception.

Was this page helpful?
0 / 5 - 0 ratings