Java tutorial
/* * Copyright 2013 Stefan Domnanovits * * Licensed 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 com.codebullets.external.party.simulator.connections.websocket.inbound; import com.codebullets.external.party.simulator.connections.ConnectionMonitor; import com.codebullets.external.party.simulator.connections.websocket.NettyConnectionContext; import com.codebullets.external.party.simulator.pipeline.MessageReceivedEvent; 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.channel.group.ChannelGroup; 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.HttpResponseStatus; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive; import static io.netty.handler.codec.http.HttpHeaders.setContentLength; 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; /** * Handles web socket frames being received.<p/> * Parts of this class are based on the WebSocketServerHandler class from netty examples. */ public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler<Object> { private static final Logger LOG = LoggerFactory.getLogger(NettyWebSocketServerHandler.class); private final URI endpoint; private final ConnectionMonitor connectionMonitor; private final String connectionName; private final ChannelGroup connectedChannels; private NettyConnectionContext context; private WebSocketServerHandshaker handshaker; /** * Generates a new instance of NettyWebSocketServerHandler. */ public NettyWebSocketServerHandler(final URI endpoint, final ConnectionMonitor connectionMonitor, final String connectionName, final ChannelGroup connectedChannels) { this.endpoint = endpoint; this.connectionMonitor = connectionMonitor; this.connectionName = connectionName; this.connectedChannels = connectedChannels; } @Override public void messageReceived(final ChannelHandlerContext ctx, final Object msg) throws Exception { if (msg instanceof FullHttpRequest) { handleHttpRequest(ctx, (FullHttpRequest) msg); } else if (msg instanceof WebSocketFrame) { handleWebSocketFrame(ctx, (WebSocketFrame) msg); } } @Override public void channelReadComplete(final ChannelHandlerContext ctx) { ctx.flush(); } private void handleHttpRequest(final ChannelHandlerContext ctx, final FullHttpRequest req) { if (!req.getDecoderResult().isSuccess()) { // Handle a bad request. sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST)); } else if (req.getMethod() != GET) { // Allow only GET methods. sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN)); } else if ("/".equals(req.getUri())) { ByteBuf content = WebSocketServerIndexPage.getContent(endpoint.toString()); FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content); res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8"); setContentLength(res, content.readableBytes()); sendHttpResponse(ctx, req, res); } else if ("/favicon.ico".equals(req.getUri())) { // Send the demo page and favicon.ico FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND); sendHttpResponse(ctx, req, res); } else { // Handshake WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(endpoint.toString(), null, false); handshaker = wsFactory.newHandshaker(req); if (handshaker == null) { WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel()); } else { handshaker.handshake(ctx.channel(), req).addListener(new ChannelFutureListener() { @Override public void operationComplete(final ChannelFuture future) throws Exception { if (future.isSuccess()) { connectedChannels.add(ctx.channel()); connectionMonitor.connectionEstablished(getContext(ctx)); } } }); } } } private void handleWebSocketFrame(final ChannelHandlerContext ctx, final WebSocketFrame frame) { // Check for closing frame if (frame instanceof CloseWebSocketFrame) { handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain()); } else if (frame instanceof PingWebSocketFrame) { ctx.channel().write(new PongWebSocketFrame(frame.content().retain())); } else if (frame instanceof TextWebSocketFrame) { String request = ((TextWebSocketFrame) frame).text(); LOG.debug("{} received {}", ctx.channel(), request); connectionMonitor.messageReceived(MessageReceivedEvent.create(getContext(ctx), request)); } else if (frame instanceof BinaryWebSocketFrame) { ByteBuf buffer = Unpooled.buffer(frame.content().capacity()); buffer.writeBytes(frame.content()); byte[] data = buffer.array(); LOG.debug("{} received {} bytes of data.", ctx.channel(), data.length); connectionMonitor.messageReceived(MessageReceivedEvent.create(getContext(ctx), data)); } else { throw new UnsupportedOperationException( String.format("%s frame types not supported", frame.getClass().getName())); } } private static void sendHttpResponse(final ChannelHandlerContext ctx, final FullHttpRequest req, final FullHttpResponse res) { // Generate an error page if response getStatus code is not OK (200). if (res.getStatus().code() != HttpResponseStatus.OK.code()) { ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8); res.content().writeBytes(buf); buf.release(); setContentLength(res, res.content().readableBytes()); } // Send the response and close the connection if necessary. ChannelFuture f = ctx.channel().writeAndFlush(res); if (!isKeepAlive(req) || res.getStatus().code() != HttpResponseStatus.OK.code()) { f.addListener(ChannelFutureListener.CLOSE); } } private NettyConnectionContext getContext(final ChannelHandlerContext ctx) { if (context == null) { context = new NettyConnectionContext(ctx.channel(), connectionName); } return context; } @Override public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception { LOG.warn("Exception on web socket server channel name={}, id={}.", connectionName, ctx.channel().id(), cause); ctx.close(); } }