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 }