From 2e6544fc0e2581f96652be02d86e777e8e0493bc Mon Sep 17 00:00:00 2001 From: Sergey Polovko Date: Sun, 28 Feb 2016 00:50:11 +0300 Subject: [PATCH] Handle only those http requests that equal to adjusted websocket path Motivation: It will be easier to support websockets in server application by using WebSocketServerProtocolHandshakeHandler class and not reinvent its functionality. But currently it handles all http requests as if they were websocket handshake requests. Modifications: Check if http request path is equals to adjusted websocket path. Fixed example of websocket server implementation. Result: WebSocketServerProtocolHandshakeHandler handles only websocket handshake requests. --- ...bSocketServerProtocolHandshakeHandler.java | 5 + .../server/WebSocketFrameHandler.java | 48 ++++++ .../server/WebSocketIndexPageHandler.java | 110 ++++++++++++ .../server/WebSocketServerHandler.java | 159 ------------------ .../server/WebSocketServerInitializer.java | 5 +- 5 files changed, 167 insertions(+), 160 deletions(-) create mode 100644 example/src/main/java/io/netty/example/http/websocketx/server/WebSocketFrameHandler.java create mode 100644 example/src/main/java/io/netty/example/http/websocketx/server/WebSocketIndexPageHandler.java delete mode 100644 example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java index 3ecd853ff5..c2fa1928da 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java @@ -53,6 +53,11 @@ class WebSocketServerProtocolHandshakeHandler @Override public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { FullHttpRequest req = (FullHttpRequest) msg; + if (!websocketPath.equals(req.getUri())) { + ctx.fireChannelRead(msg); + return; + } + try { if (req.getMethod() != GET) { sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN)); diff --git a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketFrameHandler.java b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketFrameHandler.java new file mode 100644 index 0000000000..aa27c0163e --- /dev/null +++ b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketFrameHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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.netty.example.http.websocketx.server; + +import java.util.Locale; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Echoes uppercase content of text frames. + */ +public class WebSocketFrameHandler extends SimpleChannelInboundHandler { + + private static final Logger logger = LoggerFactory.getLogger(WebSocketFrameHandler.class); + + @Override + protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception { + // ping and pong frames already handled + + if (frame instanceof TextWebSocketFrame) { + // Send the uppercase string back. + String request = ((TextWebSocketFrame) frame).text(); + logger.info("{} received {}", ctx.channel(), request); + ctx.channel().writeAndFlush(new TextWebSocketFrame(request.toUpperCase(Locale.US))); + } else { + String message = "unsupported frame type: " + frame.getClass().getName(); + throw new UnsupportedOperationException(message); + } + } +} diff --git a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketIndexPageHandler.java b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketIndexPageHandler.java new file mode 100644 index 0000000000..ecf3e135c2 --- /dev/null +++ b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketIndexPageHandler.java @@ -0,0 +1,110 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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.netty.example.http.websocketx.server; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.SimpleChannelInboundHandler; +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.ssl.SslHandler; +import io.netty.util.CharsetUtil; + +import static io.netty.handler.codec.http.HttpMethod.GET; +import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; +import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN; +import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +/** + * Outputs index page content. + */ +public class WebSocketIndexPageHandler extends SimpleChannelInboundHandler { + + private final String websocketPath; + + public WebSocketIndexPageHandler(String websocketPath) { + this.websocketPath = websocketPath; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception { + // Handle a bad request. + if (!req.getDecoderResult().isSuccess()) { + sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST)); + return; + } + + // Allow only GET methods. + if (req.getMethod() != GET) { + sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN)); + return; + } + + // Send the index page + if ("/".equals(req.getUri()) || "/index.html".equals(req.getUri())) { + String webSocketLocation = getWebSocketLocation(ctx.pipeline(), req, websocketPath); + ByteBuf content = WebSocketServerIndexPage.getContent(webSocketLocation); + FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content); + + res.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=UTF-8"); + HttpHeaders.setContentLength(res, content.readableBytes()); + + sendHttpResponse(ctx, req, res); + } else { + sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND)); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + cause.printStackTrace(); + ctx.close(); + } + + private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) { + // Generate an error page if response getStatus code is not OK (200). + if (res.getStatus().code() != 200) { + ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8); + res.content().writeBytes(buf); + buf.release(); + HttpHeaders.setContentLength(res, res.content().readableBytes()); + } + + // Send the response and close the connection if necessary. + ChannelFuture f = ctx.channel().writeAndFlush(res); + if (!HttpHeaders.isKeepAlive(req) || res.getStatus().code() != 200) { + f.addListener(ChannelFutureListener.CLOSE); + } + } + + private static String getWebSocketLocation(ChannelPipeline cp, HttpRequest req, String path) { + String protocol = "ws"; + if (cp.get(SslHandler.class) != null) { + // SSL in use so use Secure WebSockets + protocol = "wss"; + } + return protocol + "://" + req.headers().get(HttpHeaders.Names.HOST) + path; + } +} diff --git a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java deleted file mode 100644 index 87cd00482f..0000000000 --- a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2012 The Netty Project - * - * The Netty Project licenses this file to you 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.netty.example.http.websocketx.server; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; -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.websocketx.CloseWebSocketFrame; -import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; -import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; -import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; -import io.netty.handler.codec.http.websocketx.WebSocketFrame; -import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; -import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; -import io.netty.util.CharsetUtil; - -import static io.netty.handler.codec.http.HttpHeaders.Names.*; -import static io.netty.handler.codec.http.HttpMethod.*; -import static io.netty.handler.codec.http.HttpResponseStatus.*; -import static io.netty.handler.codec.http.HttpVersion.*; - -/** - * Handles handshakes and messages - */ -public class WebSocketServerHandler extends SimpleChannelInboundHandler { - - private static final String WEBSOCKET_PATH = "/websocket"; - - private WebSocketServerHandshaker handshaker; - - @Override - public void channelRead0(ChannelHandlerContext ctx, Object msg) { - if (msg instanceof FullHttpRequest) { - handleHttpRequest(ctx, (FullHttpRequest) msg); - } else if (msg instanceof WebSocketFrame) { - handleWebSocketFrame(ctx, (WebSocketFrame) msg); - } - } - - @Override - public void channelReadComplete(ChannelHandlerContext ctx) { - ctx.flush(); - } - - private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) { - // Handle a bad request. - if (!req.getDecoderResult().isSuccess()) { - sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST)); - return; - } - - // Allow only GET methods. - if (req.getMethod() != GET) { - sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN)); - return; - } - - // Send the demo page and favicon.ico - if ("/".equals(req.getUri())) { - ByteBuf content = WebSocketServerIndexPage.getContent(getWebSocketLocation(req)); - FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content); - - res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8"); - HttpHeaders.setContentLength(res, content.readableBytes()); - - sendHttpResponse(ctx, req, res); - return; - } - if ("/favicon.ico".equals(req.getUri())) { - FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND); - sendHttpResponse(ctx, req, res); - return; - } - - // Handshake - WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory( - getWebSocketLocation(req), null, true); - handshaker = wsFactory.newHandshaker(req); - if (handshaker == null) { - WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); - } else { - handshaker.handshake(ctx.channel(), req); - } - } - - private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { - - // Check for closing frame - if (frame instanceof CloseWebSocketFrame) { - handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain()); - return; - } - if (frame instanceof PingWebSocketFrame) { - ctx.channel().write(new PongWebSocketFrame(frame.content().retain())); - return; - } - if (!(frame instanceof TextWebSocketFrame)) { - throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass() - .getName())); - } - - // Send the uppercase string back. - String request = ((TextWebSocketFrame) frame).text(); - System.err.printf("%s received %s%n", ctx.channel(), request); - ctx.channel().write(new TextWebSocketFrame(request.toUpperCase())); - } - - private static void sendHttpResponse( - ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) { - // Generate an error page if response getStatus code is not OK (200). - if (res.getStatus().code() != 200) { - ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8); - res.content().writeBytes(buf); - buf.release(); - HttpHeaders.setContentLength(res, res.content().readableBytes()); - } - - // Send the response and close the connection if necessary. - ChannelFuture f = ctx.channel().writeAndFlush(res); - if (!HttpHeaders.isKeepAlive(req) || res.getStatus().code() != 200) { - f.addListener(ChannelFutureListener.CLOSE); - } - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - cause.printStackTrace(); - ctx.close(); - } - - private static String getWebSocketLocation(FullHttpRequest req) { - String location = req.headers().get(HOST) + WEBSOCKET_PATH; - if (WebSocketServer.SSL) { - return "wss://" + location; - } else { - return "ws://" + location; - } - } -} diff --git a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerInitializer.java b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerInitializer.java index f012004c2f..a4ca9131a7 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerInitializer.java +++ b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerInitializer.java @@ -26,6 +26,8 @@ import io.netty.handler.ssl.SslContext; */ public class WebSocketServerInitializer extends ChannelInitializer { + private static final String WEBSOCKET_PATH = "/websocket"; + private final SslContext sslCtx; public WebSocketServerInitializer(SslContext sslCtx) { @@ -40,6 +42,7 @@ public class WebSocketServerInitializer extends ChannelInitializer