diff --git a/README.md b/README.md index 471c15a..f44b59e 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ repositories { } dependencies { - implementation "com.jauntsdn.netty:netty-websocket-http1:0.9.1" + implementation "com.jauntsdn.netty:netty-websocket-http1:0.9.2" } ``` diff --git a/gradle.properties b/gradle.properties index 48f6768..4b913ed 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,14 +1,14 @@ group=com.jauntsdn.netty -version=0.9.2 +version=1.0.0 googleJavaFormatPluginVersion=0.9 dependencyManagementPluginVersion=1.1.0 gitPluginVersion=0.13.0 -osDetectorPluginVersion=1.7.1 -versionsPluginVersion=0.44.0 +osDetectorPluginVersion=1.7.3 +versionsPluginVersion=0.45.0 -nettyVersion=4.1.87.Final -nettyTcnativeVersion=2.0.56.Final +nettyVersion=4.1.90.Final +nettyTcnativeVersion=2.0.59.Final hdrHistogramVersion=2.1.12 slf4jVersion=1.7.36 logbackVersion=1.2.11 diff --git a/netty-websocket-http1-test/gradle.lockfile b/netty-websocket-http1-test/gradle.lockfile index e4452cb..1396fe6 100644 --- a/netty-websocket-http1-test/gradle.lockfile +++ b/netty-websocket-http1-test/gradle.lockfile @@ -9,16 +9,16 @@ com.google.errorprone:javac-shaded:9+181-r4173-1=googleJavaFormat1.6 com.google.googlejavaformat:google-java-format:1.6=googleJavaFormat1.6 com.google.guava:guava:22.0=googleJavaFormat1.6 com.google.j2objc:j2objc-annotations:1.1=googleJavaFormat1.6 -io.netty:netty-buffer:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-codec-http:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-codec:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-common:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-handler:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-resolver:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport-classes-epoll:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport-classes-kqueue:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport-native-unix-common:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-buffer:4.1.90.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-codec-http:4.1.90.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-codec:4.1.90.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-common:4.1.90.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-handler:4.1.90.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-resolver:4.1.90.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport-classes-epoll:4.1.90.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport-classes-kqueue:4.1.90.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport-native-unix-common:4.1.90.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport:4.1.90.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath net.bytebuddy:byte-buddy:1.12.21=testCompileClasspath,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath org.assertj:assertj-core:3.24.2=testCompileClasspath,testRuntimeClasspath diff --git a/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketHandshakeTest.java b/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketHandshakeTest.java index ed642e8..cca27cf 100644 --- a/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketHandshakeTest.java +++ b/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketHandshakeTest.java @@ -42,6 +42,8 @@ import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakeException; import io.netty.handler.codec.http.websocketx.WebSocketDecoderConfig; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler.HandshakeComplete; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler.ServerHandshakeStateEvent; import io.netty.util.AttributeKey; import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.DefaultPromise; @@ -49,7 +51,9 @@ import io.netty.util.concurrent.Promise; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; +import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterEach; @@ -305,9 +309,36 @@ protected void initChannel(SocketChannel ch) { Assertions.assertThat(client.isOpen()).isFalse(); } + @SuppressWarnings("deprecation") + @Timeout(15) + @Test + void serverHandshakeEvents() throws InterruptedException { + WebSocketDecoderConfig decoderConfig = webSocketDecoderConfig(false, true, 125); + TestWebSocketHandler serverHandler = new TestWebSocketHandler(); + TestWebSocketHandler clientHandler = new TestWebSocketHandler(); + String subprotocol = "subprotocol"; + String path = "/"; + Channel s = server = testServer(path, subprotocol, decoderConfig, serverHandler, null); + Channel client = + testClient(s.localAddress(), path, subprotocol, true, true, 65_535, clientHandler); + serverHandler.onOpen.join(); + client.close(); + serverHandler.onClose.join(); + List events = serverHandler.events; + Assertions.assertThat(events).hasSize(2); + Assertions.assertThat(events.get(0)).isEqualTo(ServerHandshakeStateEvent.HANDSHAKE_COMPLETE); + Object event = serverHandler.events.get(1); + Assertions.assertThat(event).isExactlyInstanceOf(HandshakeComplete.class); + HandshakeComplete completeEvent = (HandshakeComplete) event; + Assertions.assertThat(completeEvent.requestUri()).isEqualTo(path); + Assertions.assertThat(completeEvent.requestHeaders()).isNotNull().isNotEmpty(); + Assertions.assertThat(completeEvent.selectedSubprotocol()).isEqualTo(subprotocol); + } + static Channel testClient( SocketAddress address, String path, + String subprotocol, boolean mask, boolean allowMaskMismatch, int maxFramePayloadLength, @@ -331,6 +362,7 @@ protected void initChannel(SocketChannel ch) { .allowMaskMismatch(allowMaskMismatch) .maxFramePayloadLength(maxFramePayloadLength) .webSocketHandler(webSocketCallbacksHandler) + .subprotocol(subprotocol) .build(); ChannelPipeline pipeline = ch.pipeline(); @@ -342,6 +374,24 @@ protected void initChannel(SocketChannel ch) { .channel(); } + static Channel testClient( + SocketAddress address, + String path, + boolean mask, + boolean allowMaskMismatch, + int maxFramePayloadLength, + WebSocketCallbacksHandler webSocketCallbacksHandler) + throws InterruptedException { + return testClient( + address, + path, + null, + mask, + allowMaskMismatch, + maxFramePayloadLength, + webSocketCallbacksHandler); + } + static Channel testServer( String path, WebSocketDecoderConfig decoderConfig, @@ -356,12 +406,27 @@ static Channel testServer( WebSocketCallbacksHandler webSocketCallbacksHandler, Consumer nonHandledMessageConsumer) throws InterruptedException { + return testServer( + path, null, decoderConfig, webSocketCallbacksHandler, nonHandledMessageConsumer); + } + + static Channel testServer( + String path, + String subprotocol, + WebSocketDecoderConfig decoderConfig, + WebSocketCallbacksHandler webSocketCallbacksHandler, + Consumer nonHandledMessageConsumer) + throws InterruptedException { return new ServerBootstrap() .group(new NioEventLoopGroup(1)) .channel(NioServerSocketChannel.class) .childHandler( new TestAcceptor( - path, decoderConfig, webSocketCallbacksHandler, nonHandledMessageConsumer)) + path, + subprotocol, + decoderConfig, + webSocketCallbacksHandler, + nonHandledMessageConsumer)) .bind("localhost", 0) .sync() .channel(); @@ -414,16 +479,19 @@ Future response() { static class TestAcceptor extends ChannelInitializer { private final String path; + private final String subprotocol; private final WebSocketDecoderConfig webSocketDecoderConfig; private final WebSocketCallbacksHandler webSocketCallbacksHandler; private final Consumer nonHandledMessageConsumer; TestAcceptor( String path, + String subprotocol, WebSocketDecoderConfig decoderConfig, WebSocketCallbacksHandler webSocketCallbacksHandler, Consumer nonHandledMessageConsumer) { this.path = path; + this.subprotocol = subprotocol; this.webSocketDecoderConfig = decoderConfig; this.webSocketCallbacksHandler = webSocketCallbacksHandler; this.nonHandledMessageConsumer = nonHandledMessageConsumer; @@ -436,6 +504,7 @@ protected void initChannel(SocketChannel ch) { WebSocketServerProtocolHandler webSocketProtocolHandler = WebSocketServerProtocolHandler.create() .path(path) + .subprotocols(subprotocol) .decoderConfig(webSocketDecoderConfig) .webSocketCallbacksHandler(webSocketCallbacksHandler) .build(); @@ -458,6 +527,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { static class TestWebSocketHandler implements WebSocketCallbacksHandler { final CompletableFuture onOpen = new CompletableFuture<>(); final CompletableFuture onClose = new CompletableFuture<>(); + final List events = new CopyOnWriteArrayList<>(); volatile WebSocketFrameFactory webSocketFrameFactory; volatile Channel channel; @@ -477,6 +547,11 @@ public void onChannelRead( int opcode, ByteBuf payload) {} + @Override + public void onUserEventTriggered(ChannelHandlerContext ctx, Object evt) { + events.add(evt); + } + @Override public void onOpen(ChannelHandlerContext ctx) { onOpen.complete(null); diff --git a/netty-websocket-http1/gradle.lockfile b/netty-websocket-http1/gradle.lockfile index 0cf3816..fc67788 100644 --- a/netty-websocket-http1/gradle.lockfile +++ b/netty-websocket-http1/gradle.lockfile @@ -7,13 +7,13 @@ com.google.errorprone:javac-shaded:9+181-r4173-1=googleJavaFormat1.6 com.google.googlejavaformat:google-java-format:1.6=googleJavaFormat1.6 com.google.guava:guava:22.0=googleJavaFormat1.6 com.google.j2objc:j2objc-annotations:1.1=googleJavaFormat1.6 -io.netty:netty-buffer:4.1.87.Final=compileClasspath -io.netty:netty-codec-http:4.1.87.Final=compileClasspath -io.netty:netty-codec:4.1.87.Final=compileClasspath -io.netty:netty-common:4.1.87.Final=compileClasspath -io.netty:netty-handler:4.1.87.Final=compileClasspath -io.netty:netty-resolver:4.1.87.Final=compileClasspath -io.netty:netty-transport-native-unix-common:4.1.87.Final=compileClasspath -io.netty:netty-transport:4.1.87.Final=compileClasspath +io.netty:netty-buffer:4.1.90.Final=compileClasspath +io.netty:netty-codec-http:4.1.90.Final=compileClasspath +io.netty:netty-codec:4.1.90.Final=compileClasspath +io.netty:netty-common:4.1.90.Final=compileClasspath +io.netty:netty-handler:4.1.90.Final=compileClasspath +io.netty:netty-resolver:4.1.90.Final=compileClasspath +io.netty:netty-transport-native-unix-common:4.1.90.Final=compileClasspath +io.netty:netty-transport:4.1.90.Final=compileClasspath org.codehaus.mojo:animal-sniffer-annotations:1.14=googleJavaFormat1.6 empty=annotationProcessor diff --git a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketDecoder.java b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketDecoder.java index ecdb399..97eff98 100644 --- a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketDecoder.java +++ b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketDecoder.java @@ -18,9 +18,8 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandler; -abstract class WebSocketDecoder implements ChannelInboundHandler, WebSocketCallbacksFrameDecoder { +abstract class WebSocketDecoder implements WebSocketCallbacksFrameDecoder { static final int STATE_NON_PARTIAL = 0; static final int STATE_PARTIAL_PREFIX = 1; @@ -97,7 +96,6 @@ public void channelWritabilityChanged(ChannelHandlerContext ctx) { } } - @SuppressWarnings("deprecation") @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { WebSocketFrameListener listener = webSocketFrameListener; diff --git a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java index 3c3620c..d8ae34a 100644 --- a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java +++ b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java @@ -25,10 +25,12 @@ import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.websocketx.WebSocketDecoderConfig; @@ -141,13 +143,15 @@ private void completeHandshake(ChannelHandlerContext ctx, HttpRequest request) { WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); } else { ChannelPromise handshake = handshakeCompleted; + String uri = request.uri(); + HttpHeaders headers = request.headers(); ChannelFuture handshakeFuture; /*netty's websocket handshaker throws exceptions instead of notifying handshake future*/ try { handshakeFuture = handshaker.handshake(ctx.channel(), request); } catch (Exception e) { - handleHandshakeResult(ctx, handshake, e); + handleHandshakeResult(ctx, handshake, uri, headers, handshaker.selectedSubprotocol(), e); return; } ScheduledFuture timeout = startHandshakeTimeout(ctx, handshakeTimeoutMillis, handshake); @@ -156,13 +160,20 @@ private void completeHandshake(ChannelHandlerContext ctx, HttpRequest request) { if (timeout != null) { timeout.cancel(true); } - handleHandshakeResult(ctx, handshake, future.cause()); + handleHandshakeResult( + ctx, handshake, uri, headers, handshaker.selectedSubprotocol(), future.cause()); }); } } + @SuppressWarnings("deprecation") private void handleHandshakeResult( - ChannelHandlerContext ctx, ChannelPromise handshake, Throwable cause) { + ChannelHandlerContext ctx, + ChannelPromise handshake, + String uri, + HttpHeaders headers, + String subprotocol, + Throwable cause) { if (cause != null) { handshake.tryFailure(cause); if (cause instanceof WebSocketHandshakeException) { @@ -179,9 +190,13 @@ private void handleHandshakeResult( } else { WebSocketCallbacksHandler.exchange(ctx, webSocketHandler); handshake.trySuccess(); - ctx.fireUserEventTriggered( + ChannelPipeline p = ctx.channel().pipeline(); + p.fireUserEventTriggered( io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler .ServerHandshakeStateEvent.HANDSHAKE_COMPLETE); + p.fireUserEventTriggered( + new io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler + .HandshakeComplete(uri, headers, subprotocol)); } ctx.pipeline().remove(this); }