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