diff --git a/webserver/tyrus/pom.xml b/webserver/tyrus/pom.xml index 8fc0491df5e..7007426b673 100644 --- a/webserver/tyrus/pom.xml +++ b/webserver/tyrus/pom.xml @@ -85,6 +85,24 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + default-testCompile + + + + --add-modules + java.net.http + + + + + maven-surefire-plugin diff --git a/webserver/tyrus/src/test/java/io/helidon/webserver/tyrus/HttpClientTest.java b/webserver/tyrus/src/test/java/io/helidon/webserver/tyrus/HttpClientTest.java new file mode 100644 index 00000000000..0a0c797a67a --- /dev/null +++ b/webserver/tyrus/src/test/java/io/helidon/webserver/tyrus/HttpClientTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.webserver.tyrus; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.WebSocket; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.websocket.server.ServerEndpointConfig; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class HttpClientTest extends TyrusSupportBaseTest { + @BeforeAll + static void startServer() throws ExecutionException, InterruptedException, TimeoutException { + ServerEndpointConfig.Builder builder = ServerEndpointConfig.Builder.create( + EchoEndpoint.class, "/"); + webServer(true, builder.build()); + } + + @Test + void testJdkClient() throws ExecutionException, InterruptedException, TimeoutException { + URI uri = URI.create("ws://localhost:" + webServer().port() + "/tyrus/echo"); + ClientListener listener = new ClientListener(); + + WebSocket webSocket = HttpClient.newHttpClient() + .newWebSocketBuilder() + .buildAsync(uri, listener) + .get(); + + webSocket.sendText("message", true); + webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "finished"); + + String response = listener.await(); + assertThat(response, is("message")); + } + + private static class ClientListener implements WebSocket.Listener { + private final CompletableFuture future = new CompletableFuture<>(); + + @Override + public CompletionStage onText(WebSocket webSocket, CharSequence data, boolean last) { + future.complete(data.toString()); + return CompletableFuture.completedFuture(data); + } + + @Override + public CompletionStage onClose(WebSocket webSocket, int statusCode, String reason) { + future.complete(reason); + return CompletableFuture.completedFuture(reason); + } + + @Override + public void onError(WebSocket webSocket, Throwable error) { + future.completeExceptionally(error); + } + + public String await() throws ExecutionException, InterruptedException, TimeoutException { + return future.get(10, TimeUnit.SECONDS); + } + } +} diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/ForwardingHandler.java b/webserver/webserver/src/main/java/io/helidon/webserver/ForwardingHandler.java index 030a9cc9523..240fd58332f 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/ForwardingHandler.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/ForwardingHandler.java @@ -367,7 +367,9 @@ private boolean channelReadHttpRequest(ChannelHandlerContext ctx, Context reques String contentLength = request.headers().get(HttpHeaderNames.CONTENT_LENGTH); + // HTTP WebSocket client sends a content length of 0 together with Connection: Upgrade if ("0".equals(contentLength) + && !"upgrade".equalsIgnoreCase(request.headers().get(HttpHeaderNames.CONNECTION)) || (contentLength == null && !"upgrade".equalsIgnoreCase(request.headers().get(HttpHeaderNames.CONNECTION)) && !"chunked".equalsIgnoreCase(request.headers().get(HttpHeaderNames.TRANSFER_ENCODING))