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