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