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 org.jboss.netty.example.http.websocketx.server;
17  
18  import org.jboss.netty.buffer.ChannelBuffer;
19  import org.jboss.netty.buffer.ChannelBuffers;
20  import org.jboss.netty.channel.ChannelFuture;
21  import org.jboss.netty.channel.ChannelFutureListener;
22  import org.jboss.netty.channel.ChannelHandlerContext;
23  import org.jboss.netty.channel.ExceptionEvent;
24  import org.jboss.netty.channel.MessageEvent;
25  import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
26  import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
27  import org.jboss.netty.handler.codec.http.HttpRequest;
28  import org.jboss.netty.handler.codec.http.HttpResponse;
29  import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
30  import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame;
31  import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame;
32  import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame;
33  import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
34  import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
35  import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
36  import org.jboss.netty.logging.InternalLogger;
37  import org.jboss.netty.logging.InternalLoggerFactory;
38  import org.jboss.netty.util.CharsetUtil;
39  
40  import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*;
41  import static org.jboss.netty.handler.codec.http.HttpHeaders.*;
42  import static org.jboss.netty.handler.codec.http.HttpMethod.*;
43  import static org.jboss.netty.handler.codec.http.HttpResponseStatus.*;
44  import static org.jboss.netty.handler.codec.http.HttpVersion.*;
45  
46  /**
47   * Handles handshakes and messages
48   */
49  public class WebSocketServerHandler extends SimpleChannelUpstreamHandler {
50      private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandler.class);
51  
52      private static final String WEBSOCKET_PATH = "/websocket";
53  
54      private WebSocketServerHandshaker handshaker;
55  
56      @Override
57      public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
58          Object msg = e.getMessage();
59          if (msg instanceof HttpRequest) {
60              handleHttpRequest(ctx, (HttpRequest) msg);
61          } else if (msg instanceof WebSocketFrame) {
62              handleWebSocketFrame(ctx, (WebSocketFrame) msg);
63          }
64      }
65  
66      private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
67          // Allow only GET methods.
68          if (req.getMethod() != GET) {
69              sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN));
70              return;
71          }
72  
73          // Send the demo page and favicon.ico
74          if ("/".equals(req.getUri())) {
75              HttpResponse res = new DefaultHttpResponse(HTTP_1_1, OK);
76  
77              ChannelBuffer content = WebSocketServerIndexPage.getContent(getWebSocketLocation(req));
78  
79              res.setHeader(CONTENT_TYPE, "text/html; charset=UTF-8");
80              setContentLength(res, content.readableBytes());
81  
82              res.setContent(content);
83              sendHttpResponse(ctx, req, res);
84              return;
85          }
86          if ("/favicon.ico".equals(req.getUri())) {
87              HttpResponse res = new DefaultHttpResponse(HTTP_1_1, NOT_FOUND);
88              sendHttpResponse(ctx, req, res);
89              return;
90          }
91  
92          // Handshake
93          WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
94                  getWebSocketLocation(req), null, false);
95          handshaker = wsFactory.newHandshaker(req);
96          if (handshaker == null) {
97              wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel());
98          } else {
99              handshaker.handshake(ctx.getChannel(), req).addListener(WebSocketServerHandshaker.HANDSHAKE_LISTENER);
100         }
101     }
102 
103     private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
104 
105         // Check for closing frame
106         if (frame instanceof CloseWebSocketFrame) {
107             handshaker.close(ctx.getChannel(), (CloseWebSocketFrame) frame);
108             return;
109         }
110         if (frame instanceof PingWebSocketFrame) {
111             ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData()));
112             return;
113         }
114         if (!(frame instanceof TextWebSocketFrame)) {
115             throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass()
116                     .getName()));
117         }
118 
119         // Send the uppercase string back.
120         String request = ((TextWebSocketFrame) frame).getText();
121         if (logger.isDebugEnabled()) {
122             logger.debug(String.format("Channel %s received %s", ctx.getChannel().getId(), request));
123         }
124         ctx.getChannel().write(new TextWebSocketFrame(request.toUpperCase()));
125     }
126 
127     private static void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
128         // Generate an error page if response status code is not OK (200).
129         if (res.getStatus().getCode() != 200) {
130             res.setContent(ChannelBuffers.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8));
131             setContentLength(res, res.getContent().readableBytes());
132         }
133 
134         // Send the response and close the connection if necessary.
135         ChannelFuture f = ctx.getChannel().write(res);
136         if (!isKeepAlive(req) || res.getStatus().getCode() != 200) {
137             f.addListener(ChannelFutureListener.CLOSE);
138         }
139     }
140 
141     @Override
142     public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
143         e.getCause().printStackTrace();
144         e.getChannel().close();
145     }
146 
147     private static String getWebSocketLocation(HttpRequest req) {
148         return "ws://" + req.getHeader(HOST) + WEBSOCKET_PATH;
149     }
150 }