0

I would need to implement a HTTP server where a certain URI allows upgrading to a WebSocket connection. The standard WebSocket handler class is this:

https://netty.io/4.0/xref/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.html

The JavaDoc mentions that the class is only meant to support WebSockets and if one wants to also support HTTP requests on the same socket, then one should refer to the following example:

https://netty.io/4.0/xref/io/netty/example/http/websocketx/server/WebSocketServer.html

However, the above example actually uses the WebSocketServerProtocolHandler class... Is there an up-to-date example for how to do this?

eof
  • 413
  • 4
  • 14

2 Answers2

1

In order to support both raw HTTP and WebSocket protocols, you would need to implement a custom io.netty.channel.ChannelInitializer where you would insert an HttpRequestHandler and a WebSocketServerProtocolHandler (along with needed encoding and decoding handlers) to support WebSocket protocol upgrade on a custom uri:

public class ServerInitializer extends ChannelInitializer<SocketChannel> {

    private static final String WEBSOCKET_PATH = "/ws";

    private final SslContext sslCtx;

    public WebSocketServerInitializer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        if (sslCtx != null) {
            pipeline.addLast(sslCtx.newHandler(ch.alloc()));
        }
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new HttpObjectAggregator(65536));
        pipeline.addLast(new HttpRequestHandler(WEBSOCKET_PATH));
        pipeline.addLast(new WebSocketServerCompressionHandler());
        pipeline.addLast(new WebSocketServerProtocolHandler(WEBSOCKET_PATH, null, true));
        pipeline.addLast(new WebSocketIndexPageHandler(WEBSOCKET_PATH));
        pipeline.addLast(new WebSocketFrameHandler());
    }
}

Here is a sample of how the HttpRequestHandler would look like:

public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private final String websocketUri;

    public HttpRequestHandler(String wsUri) {
        websockeUri = wsUri;
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        if (this. websocketUri.equalsIgnoreCase(request.getUri())) { // if the request uri matches the web socket path, we forward to next handler which will handle the upgrade handshake
            ctx.fireChannelRead(request.retain()); // we need to increment the reference count to retain the ByteBuf for upcoming processing
        } else {
            // Otherwise, process your HTTP request and send the flush the response
            HttpResponse response = new DefaultHttpResponse(
                request.getProtocolVersion(), HttpResponseStatus.OK);
            response.headers().set(
                HttpHeaders.Names.CONTENT_TYPE,
                "text/html; charset=UTF-8");
            ctx.write(response);
            ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
        throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

Here down the implementation of the WebSocketHandler echoing back the frame text in uppercase:

public class WebSocketFrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> {

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        // If the WebSocket handshake was successful, we remove the HttpRequestHandler from the pipeline as we are no more supporting raw HTTP requests
        if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
            ctx.pipeline().remove(HttpRequestHandler.class);
        } else {
            // otherwise forward to next handler
            super.userEventTriggered(ctx, evt);
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
        if (frame instanceof TextWebSocketFrame) {
            // Send the uppercase string back.
            String request = ((TextWebSocketFrame) frame).text();
            ctx.channel().writeAndFlush(new TextWebSocketFrame(request.toUpperCase(Locale.US)));
        } else {
            String message = "unsupported frame type: " + frame.getClass().getName();
            throw new UnsupportedOperationException(message);
        }
    }
}
tmarwen
  • 15,750
  • 5
  • 43
  • 62
0

Practically,we should use independent ports for different protocols.When http upgrades to WebSocket,we can not send http msg at all,because the msg bytes in http and WebSocket are different.

老菜鸟
  • 15
  • 6