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