View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.example.http.websocketx.server;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.Unpooled;
20  import io.netty.channel.ChannelFuture;
21  import io.netty.channel.ChannelFutureListener;
22  import io.netty.channel.ChannelHandlerContext;
23  import io.netty.channel.SimpleChannelInboundHandler;
24  import io.netty.handler.codec.http.DefaultFullHttpResponse;
25  import io.netty.handler.codec.http.FullHttpRequest;
26  import io.netty.handler.codec.http.FullHttpResponse;
27  import io.netty.handler.codec.http.HttpHeaderUtil;
28  import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
29  import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
30  import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
31  import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
32  import io.netty.handler.codec.http.websocketx.WebSocketFrame;
33  import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
34  import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
35  import io.netty.util.CharsetUtil;
36  
37  import static io.netty.handler.codec.http.HttpHeaderNames.*;
38  import static io.netty.handler.codec.http.HttpMethod.*;
39  import static io.netty.handler.codec.http.HttpResponseStatus.*;
40  import static io.netty.handler.codec.http.HttpVersion.*;
41  
42  /**
43   * Handles handshakes and messages
44   */
45  public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
46  
47      private static final String WEBSOCKET_PATH = "/websocket";
48  
49      private WebSocketServerHandshaker handshaker;
50  
51      @Override
52      public void messageReceived(ChannelHandlerContext ctx, Object msg) {
53          if (msg instanceof FullHttpRequest) {
54              handleHttpRequest(ctx, (FullHttpRequest) msg);
55          } else if (msg instanceof WebSocketFrame) {
56              handleWebSocketFrame(ctx, (WebSocketFrame) msg);
57          }
58      }
59  
60      @Override
61      public void channelReadComplete(ChannelHandlerContext ctx) {
62          ctx.flush();
63      }
64  
65      private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
66          // Handle a bad request.
67          if (!req.decoderResult().isSuccess()) {
68              sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
69              return;
70          }
71  
72          // Allow only GET methods.
73          if (req.method() != GET) {
74              sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN));
75              return;
76          }
77  
78          // Send the demo page and favicon.ico
79          if ("/".equals(req.uri())) {
80              ByteBuf content = WebSocketServerIndexPage.getContent(getWebSocketLocation(req));
81              FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content);
82  
83              res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
84              HttpHeaderUtil.setContentLength(res, content.readableBytes());
85  
86              sendHttpResponse(ctx, req, res);
87              return;
88          }
89          if ("/favicon.ico".equals(req.uri())) {
90              FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND);
91              sendHttpResponse(ctx, req, res);
92              return;
93          }
94  
95          // Handshake
96          WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
97                  getWebSocketLocation(req), null, true);
98          handshaker = wsFactory.newHandshaker(req);
99          if (handshaker == null) {
100             WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
101         } else {
102             handshaker.handshake(ctx.channel(), req);
103         }
104     }
105 
106     private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
107 
108         // Check for closing frame
109         if (frame instanceof CloseWebSocketFrame) {
110             handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
111             return;
112         }
113         if (frame instanceof PingWebSocketFrame) {
114             ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
115             return;
116         }
117         if (!(frame instanceof TextWebSocketFrame)) {
118             throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass()
119                     .getName()));
120         }
121 
122         // Send the uppercase string back.
123         String request = ((TextWebSocketFrame) frame).text();
124         System.err.printf("%s received %s%n", ctx.channel(), request);
125         ctx.channel().write(new TextWebSocketFrame(request.toUpperCase()));
126     }
127 
128     private static void sendHttpResponse(
129             ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
130         // Generate an error page if response getStatus code is not OK (200).
131         if (res.status().code() != 200) {
132             ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
133             res.content().writeBytes(buf);
134             buf.release();
135             HttpHeaderUtil.setContentLength(res, res.content().readableBytes());
136         }
137 
138         // Send the response and close the connection if necessary.
139         ChannelFuture f = ctx.channel().writeAndFlush(res);
140         if (!HttpHeaderUtil.isKeepAlive(req) || res.status().code() != 200) {
141             f.addListener(ChannelFutureListener.CLOSE);
142         }
143     }
144 
145     @Override
146     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
147         cause.printStackTrace();
148         ctx.close();
149     }
150 
151     private static String getWebSocketLocation(FullHttpRequest req) {
152         String location =  req.headers().get(HOST) + WEBSOCKET_PATH;
153         if (WebSocketServer.SSL) {
154             return "wss://" + location;
155         } else {
156             return "ws://" + location;
157         }
158     }
159 }