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