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.handler.codec.http.websocketx;
    17
    18  import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*;
    19  import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.*;
    20  import static org.jboss.netty.handler.codec.http.HttpVersion.*;
    21
    22  import org.jboss.netty.buffer.ChannelBuffer;
    23  import org.jboss.netty.buffer.ChannelBuffers;
    24  import org.jboss.netty.channel.Channel;
    25  import org.jboss.netty.channel.ChannelFuture;
    26  import org.jboss.netty.channel.ChannelPipeline;
    27  import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
    28  import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
    29  import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
    30  import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
    31  import org.jboss.netty.handler.codec.http.HttpRequest;
    32  import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
    33  import org.jboss.netty.handler.codec.http.HttpResponse;
    34  import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
    35  import org.jboss.netty.handler.codec.http.HttpResponseStatus;
    36  import org.jboss.netty.logging.InternalLogger;
    37  import org.jboss.netty.logging.InternalLoggerFactory;
    38
    39  /**
    40   * <p>
    41   * Performs server side opening and closing handshakes for web socket specification version <a
    42   * href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00" >draft-ietf-hybi-thewebsocketprotocol-
    43   * 00</a>
    44   * </p>
    45   * <p>
    46   * A very large portion of this code was taken from the Netty 3.2 HTTP example.
    47   * </p>
    48   */
    49  public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
    50
    51      private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker00.class);
    52
    53      /**
    54       * Constructor with default values
    55       *
    56       * @param webSocketURL
    57       *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
    58       *            sent to this URL.
    59       * @param subprotocols
    60       *            CSV of supported protocols
    61       */
    62      public WebSocketServerHandshaker00(String webSocketURL, String subprotocols) {
    63          this(webSocketURL, subprotocols, Long.MAX_VALUE);
    64      }
    65
    66      /**
    67        * Constructor specifying the destination web socket location
    68        *
    69        * @param webSocketURL
    70        *            URL for web socket communications. e.g "ws://myhost.com/mypath".
    71        *            Subsequent web socket frames will be sent to this URL.
    72        * @param subprotocols
    73        *            CSV of supported protocols
    74        * @param maxFramePayloadLength
    75        *            Maximum allowable frame payload length. Setting this value to your application's requirement may
    76        *            reduce denial of service attacks using long data frames.
    77        */
    78       public WebSocketServerHandshaker00(String webSocketURL, String subprotocols, long maxFramePayloadLength) {
    79           super(WebSocketVersion.V00, webSocketURL, subprotocols, maxFramePayloadLength);
    80       }
    81
    82      /**
    83       * <p>
    84       * Handle the web socket handshake for the web socket specification <a href=
    85       * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">HyBi version 0</a> and lower. This standard
    86       * is really a rehash of <a href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76" >hixie-76</a> and
    87       * <a href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75" >hixie-75</a>.
    88       * </p>
    89       *
    90       * <p>
    91       * Browser request to the server:
    92       * </p>
    93       *
    94       * <pre>
    95       * GET /demo HTTP/1.1
    96       * Upgrade: WebSocket
    97       * Connection: Upgrade
    98       * Host: example.com
    99       * Origin: http://example.com
    100      * Sec-WebSocket-Protocol: chat, sample
    101      * Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
    102      * Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
    103      *
    104      * ^n:ds[4U
    105      * </pre>
    106      *
    107      * <p>
    108      * Server response:
    109      * </p>
    110      *
    111      * <pre>
    112      * HTTP/1.1 101 WebSocket Protocol Handshake
    113      * Upgrade: WebSocket
    114      * Connection: Upgrade
    115      * Sec-WebSocket-Origin: http://example.com
    116      * Sec-WebSocket-Location: ws://example.com/demo
    117      * Sec-WebSocket-Protocol: sample
    118      *
    119      * 8jKS'y:G*Co,Wxa-
    120      * </pre>
    121      *
    122      * @param channel
    123      *            Channel
    124      * @param req
    125      *            HTTP request
    126      */
    127     @Override
    128     public ChannelFuture handshake(Channel channel, HttpRequest req) {
    129
    130         if (logger.isDebugEnabled()) {
    131             logger.debug(String.format("Channel %s WS Version 00 server handshake", channel.getId()));
    132         }
    133
    134         // Serve the WebSocket handshake request.
    135         if (!Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION))
    136                 || !WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) {
    137             throw new WebSocketHandshakeException("not a WebSocket handshake request: missing upgrade");
    138         }
    139
    140         // Hixie 75 does not contain these headers while Hixie 76 does
    141         boolean isHixie76 = req.containsHeader(SEC_WEBSOCKET_KEY1) && req.containsHeader(SEC_WEBSOCKET_KEY2);
    142
    143         // Create the WebSocket handshake response.
    144         HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101,
    145                 isHixie76 ? "WebSocket Protocol Handshake" : "Web Socket Protocol Handshake"));
    146         res.addHeader(Names.UPGRADE, WEBSOCKET);
    147         res.addHeader(CONNECTION, Values.UPGRADE);
    148
    149         // Fill in the headers and contents depending on handshake method.
    150         if (isHixie76) {
    151             // New handshake method with a challenge:
    152             res.addHeader(SEC_WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
    153             res.addHeader(SEC_WEBSOCKET_LOCATION, getWebSocketUrl());
    154             String subprotocols = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
    155             if (subprotocols != null) {
    156                 String selectedSubprotocol = selectSubprotocol(subprotocols);
    157                 if (selectedSubprotocol == null) {
    158                     throw new WebSocketHandshakeException("Requested subprotocol(s) not supported: " + subprotocols);
    159                 } else {
    160                     res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol);
    161                     setSelectedSubprotocol(selectedSubprotocol);
    162                 }
    163             }
    164
    165             // Calculate the answer of the challenge.
    166             String key1 = req.getHeader(SEC_WEBSOCKET_KEY1);
    167             String key2 = req.getHeader(SEC_WEBSOCKET_KEY2);
    168             int a = (int) (Long.parseLong(key1.replaceAll("[^0-9]", "")) / key1.replaceAll("[^ ]", "").length());
    169             int b = (int) (Long.parseLong(key2.replaceAll("[^0-9]", "")) / key2.replaceAll("[^ ]", "").length());
    170             long c = req.getContent().readLong();
    171             ChannelBuffer input = ChannelBuffers.buffer(16);
    172             input.writeInt(a);
    173             input.writeInt(b);
    174             input.writeLong(c);
    175             ChannelBuffer output = ChannelBuffers.wrappedBuffer(WebSocketUtil.md5(input.array()));
    176             res.setContent(output);
    177         } else {
    178             // Old Hixie 75 handshake method with no challenge:
    179             res.addHeader(WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
    180             res.addHeader(WEBSOCKET_LOCATION, getWebSocketUrl());
    181             String protocol = req.getHeader(WEBSOCKET_PROTOCOL);
    182             if (protocol != null) {
    183                 res.addHeader(WEBSOCKET_PROTOCOL, selectSubprotocol(protocol));
    184             }
    185         }
    186
    187         // Upgrade the connection and send the handshake response.
    188         ChannelPipeline p = channel.getPipeline();
    189         if (p.get(HttpChunkAggregator.class) != null) {
    190             p.remove(HttpChunkAggregator.class);
    191         }
    192         p.replace(HttpRequestDecoder.class, "wsdecoder",
    193                 new WebSocket00FrameDecoder(getMaxFramePayloadLength()));
    194
    195         ChannelFuture future = channel.write(res);
    196
    197         p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket00FrameEncoder());
    198
    199         return future;
    200     }
    201
    202     /**
    203      * Echo back the closing frame
    204      *
    205      * @param channel
    206      *            Channel
    207      * @param frame
    208      *            Web Socket frame that was received
    209      */
    210     @Override
    211     public ChannelFuture close(Channel channel, CloseWebSocketFrame frame) {
    212         return channel.write(frame);
    213     }
    214 }