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