Provide extra info together with handshake complete event.

Motivation:

As described in #5734

Before this change, if the server had to do some sort of setup after a
handshake was completed based on handshake's information, the only way
available was to wait (in a separate thread) for the handshaker to be
added as an attribute to the channel. Too much hassle.

Modifications:

Handshake completed event need to be stateful now, so I've added a tiny
class holding just the HTTP upgrade request and the selected subprotocol
which is fired as an event after the handshake has finished.
I've also deprecated the old enum used as stateless event and I left the
code that fires it for backward compatibility. It should be removed in
the next mayor release.

Result:

It should be much simpler now to do initialization stuff based on
subprotocol or request headers on handshake completion. No asynchronous
waiting needed anymore.
This commit is contained in:
Gaston Tonietti 2016-08-26 02:41:34 +10:00 committed by Norman Maurer
parent d2389a9339
commit 245fb52c90
3 changed files with 51 additions and 6 deletions

View File

@ -26,6 +26,7 @@ import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
@ -46,8 +47,9 @@ import static io.netty.handler.codec.http.HttpVersion.*;
* to the <tt>io.netty.example.http.websocketx.server.WebSocketServer</tt> example. * to the <tt>io.netty.example.http.websocketx.server.WebSocketServer</tt> example.
* *
* To know once a handshake was done you can intercept the * To know once a handshake was done you can intercept the
* {@link ChannelInboundHandler#userEventTriggered(ChannelHandlerContext, Object)} and check if the event was of type * {@link ChannelInboundHandler#userEventTriggered(ChannelHandlerContext, Object)} and check if the event was instance
* {@link ServerHandshakeStateEvent#HANDSHAKE_COMPLETE}. * of {@link HandshakeComplete}, the event will contain extra information about the handshake such as the request and
* selected subprotocol.
*/ */
public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler { public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler {
@ -56,11 +58,42 @@ public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler {
*/ */
public enum ServerHandshakeStateEvent { public enum ServerHandshakeStateEvent {
/** /**
* The Handshake was complete succesful and so the channel was upgraded to websockets * The Handshake was completed successfully and the channel was upgraded to websockets.
*
* @deprecated in favor of {@link HandshakeComplete} class,
* it provides extra information about the handshake
*/ */
@Deprecated
HANDSHAKE_COMPLETE HANDSHAKE_COMPLETE
} }
/**
* The Handshake was completed successfully and the channel was upgraded to websockets.
*/
public static final class HandshakeComplete {
private final String requestUri;
private final HttpHeaders requestHeaders;
private final String selectedSubprotocol;
HandshakeComplete(String requestUri, HttpHeaders requestHeaders, String selectedSubprotocol) {
this.requestUri = requestUri;
this.requestHeaders = requestHeaders;
this.selectedSubprotocol = selectedSubprotocol;
}
public String requestUri() {
return requestUri;
}
public HttpHeaders requestHeaders() {
return requestHeaders;
}
public String selectedSubprotocol() {
return selectedSubprotocol;
}
}
private static final AttributeKey<WebSocketServerHandshaker> HANDSHAKER_ATTR_KEY = private static final AttributeKey<WebSocketServerHandshaker> HANDSHAKER_ATTR_KEY =
AttributeKey.valueOf(WebSocketServerHandshaker.class, "HANDSHAKER"); AttributeKey.valueOf(WebSocketServerHandshaker.class, "HANDSHAKER");

View File

@ -54,7 +54,7 @@ class WebSocketServerProtocolHandshakeHandler extends ChannelInboundHandlerAdapt
@Override @Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
FullHttpRequest req = (FullHttpRequest) msg; final FullHttpRequest req = (FullHttpRequest) msg;
if (!websocketPath.equals(req.uri())) { if (!websocketPath.equals(req.uri())) {
ctx.fireChannelRead(msg); ctx.fireChannelRead(msg);
return; return;
@ -80,8 +80,12 @@ class WebSocketServerProtocolHandshakeHandler extends ChannelInboundHandlerAdapt
if (!future.isSuccess()) { if (!future.isSuccess()) {
ctx.fireExceptionCaught(future.cause()); ctx.fireExceptionCaught(future.cause());
} else { } else {
// Kept for compatibility
ctx.fireUserEventTriggered( ctx.fireUserEventTriggered(
WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE); WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE);
ctx.fireUserEventTriggered(
new WebSocketServerProtocolHandler.HandshakeComplete(
req.uri(), req.headers(), handshaker.selectedSubprotocol()));
} }
} }
}); });

View File

@ -36,12 +36,14 @@ import static org.junit.Assert.*;
public class WebSocketHandshakeHandOverTest { public class WebSocketHandshakeHandOverTest {
private boolean serverReceivedHandshake; private boolean serverReceivedHandshake;
private WebSocketServerProtocolHandler.HandshakeComplete serverHandshakeComplete;
private boolean clientReceivedHandshake; private boolean clientReceivedHandshake;
private boolean clientReceivedMessage; private boolean clientReceivedMessage;
@Before @Before
public void setUp() { public void setUp() {
serverReceivedHandshake = false; serverReceivedHandshake = false;
serverHandshakeComplete = null;
clientReceivedHandshake = false; clientReceivedHandshake = false;
clientReceivedMessage = false; clientReceivedMessage = false;
} }
@ -55,6 +57,8 @@ public class WebSocketHandshakeHandOverTest {
serverReceivedHandshake = true; serverReceivedHandshake = true;
// immediatly send a message to the client on connect // immediatly send a message to the client on connect
ctx.writeAndFlush(new TextWebSocketFrame("abc")); ctx.writeAndFlush(new TextWebSocketFrame("abc"));
} else if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
serverHandshakeComplete = (WebSocketServerProtocolHandler.HandshakeComplete) evt;
} }
} }
@Override @Override
@ -80,6 +84,10 @@ public class WebSocketHandshakeHandOverTest {
// Transfer the handshake from the client to the server // Transfer the handshake from the client to the server
transferAllDataWithMerge(clientChannel, serverChannel); transferAllDataWithMerge(clientChannel, serverChannel);
assertTrue(serverReceivedHandshake); assertTrue(serverReceivedHandshake);
assertNotNull(serverHandshakeComplete);
assertEquals("/test", serverHandshakeComplete.requestUri());
assertEquals(8, serverHandshakeComplete.requestHeaders().size());
assertEquals("test-proto-2", serverHandshakeComplete.selectedSubprotocol());
// Transfer the handshake response and the websocket message to the client // Transfer the handshake response and the websocket message to the client
transferAllDataWithMerge(serverChannel, clientChannel); transferAllDataWithMerge(serverChannel, clientChannel);
@ -124,7 +132,7 @@ public class WebSocketHandshakeHandOverTest {
new HttpClientCodec(), new HttpClientCodec(),
new HttpObjectAggregator(8192), new HttpObjectAggregator(8192),
new WebSocketClientProtocolHandler(new URI("ws://localhost:1234/test"), new WebSocketClientProtocolHandler(new URI("ws://localhost:1234/test"),
WebSocketVersion.V13, null, WebSocketVersion.V13, "test-proto-2",
false, null, 65536), false, null, 65536),
handler); handler);
} }
@ -133,7 +141,7 @@ public class WebSocketHandshakeHandOverTest {
return new EmbeddedChannel( return new EmbeddedChannel(
new HttpServerCodec(), new HttpServerCodec(),
new HttpObjectAggregator(8192), new HttpObjectAggregator(8192),
new WebSocketServerProtocolHandler("/test", null, false), new WebSocketServerProtocolHandler("/test", "test-proto-1, test-proto-2", false),
handler); handler);
} }
} }