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.channel.ChannelFutureListener;
23 import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
24 import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
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 import org.jboss.netty.util.CharsetUtil;
31
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 <a
38 * href="http://tools.ietf.org/html/rfc6455 ">RFC 6455</a> (originally web
39 * socket specification version <a
40 * href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17"
41 * >draft-ietf-hybi-thewebsocketprotocol- 17</a>).
42 * </p>
43 */
44 public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
45
46 private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker13.class);
47
48 public static final String WEBSOCKET_13_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
49
50 private final boolean allowExtensions;
51
52 /**
53 * Constructor using defaults
54 *
55 * @param webSocketURL
56 * URL for web socket communications. e.g
57 * "ws://myhost.com/mypath". Subsequent web socket frames will be
58 * sent to this URL.
59 * @param subprotocols
60 * CSV of supported protocols
61 * @param allowExtensions
62 * Allow extensions to be used in the reserved bits of the web
63 * socket frame
64 */
65 public WebSocketServerHandshaker13(String webSocketURL, String subprotocols, boolean allowExtensions) {
66 this(webSocketURL, subprotocols, allowExtensions, Long.MAX_VALUE);
67 }
68
69 /**
70 * Constructor specifying the destination web socket location
71 *
72 * @param webSocketURL
73 * URL for web socket communications. e.g
74 * "ws://myhost.com/mypath". Subsequent web socket frames will be
75 * sent to this URL.
76 * @param subprotocols
77 * CSV of supported protocols
78 * @param allowExtensions
79 * Allow extensions to be used in the reserved bits of the web
80 * socket frame
81 * @param maxFramePayloadLength
82 * Maximum allowable frame payload length. Setting this value to
83 * your application's requirement may reduce denial of service
84 * attacks using long data frames.
85 */
86 public WebSocketServerHandshaker13(String webSocketURL, String subprotocols, boolean allowExtensions,
87 long maxFramePayloadLength) {
88 super(WebSocketVersion.V13, webSocketURL, subprotocols, maxFramePayloadLength);
89 this.allowExtensions = allowExtensions;
90 }
91
92 /**
93 * <p>
94 * Handle the web socket handshake for the web socket specification <a href=
95 * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">HyBi
96 * versions 13-17</a>. Versions 13-17 share the same wire protocol.
97 * </p>
98 *
99 * <p>
100 * Browser request to the server:
101 * </p>
102 *
103 * <pre>
104 * GET /chat HTTP/1.1
105 * Host: server.example.com
106 * Upgrade: websocket
107 * Connection: Upgrade
108 * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
109 * Sec-WebSocket-Origin: http://example.com
110 * Sec-WebSocket-Protocol: chat, superchat
111 * Sec-WebSocket-Version: 13
112 * </pre>
113 *
114 * <p>
115 * Server response:
116 * </p>
117 *
118 * <pre>
119 * HTTP/1.1 101 Switching Protocols
120 * Upgrade: websocket
121 * Connection: Upgrade
122 * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
123 * Sec-WebSocket-Protocol: chat
124 * </pre>
125 *
126 * @param channel
127 * Channel
128 * @param req
129 * HTTP request
130 */
131 @Override
132 public ChannelFuture handshake(Channel channel, HttpRequest req) {
133
134 if (logger.isDebugEnabled()) {
135 logger.debug(String.format("Channel %s WS Version 13 server handshake", channel.getId()));
136 }
137
138 HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS);
139
140 String key = req.headers().get(Names.SEC_WEBSOCKET_KEY);
141 if (key == null) {
142 throw new WebSocketHandshakeException("not a WebSocket request: missing key");
143 }
144 String acceptSeed = key + WEBSOCKET_13_ACCEPT_GUID;
145 ChannelBuffer sha1 = WebSocketUtil.sha1(ChannelBuffers.copiedBuffer(acceptSeed, CharsetUtil.US_ASCII));
146 String accept = WebSocketUtil.base64(sha1);
147
148 if (logger.isDebugEnabled()) {
149 logger.debug(String.format("WS Version 13 Server Handshake key: %s. Response: %s.", key, accept));
150 }
151
152 res.setStatus(HttpResponseStatus.SWITCHING_PROTOCOLS);
153 res.headers().add(Names.UPGRADE, WEBSOCKET.toLowerCase());
154 res.headers().add(Names.CONNECTION, Names.UPGRADE);
155 res.headers().add(Names.SEC_WEBSOCKET_ACCEPT, accept);
156 String subprotocols = req.headers().get(Names.SEC_WEBSOCKET_PROTOCOL);
157 if (subprotocols != null) {
158 String selectedSubprotocol = selectSubprotocol(subprotocols);
159 if (selectedSubprotocol == null) {
160 throw new WebSocketHandshakeException("Requested subprotocol(s) not supported: " + subprotocols);
161 } else {
162 res.headers().add(Names.SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol);
163 setSelectedSubprotocol(selectedSubprotocol);
164 }
165 }
166
167 return writeHandshakeResponse(
168 channel, res, new WebSocket13FrameEncoder(false),
169 new WebSocket13FrameDecoder(true, allowExtensions, getMaxFramePayloadLength()));
170 }
171
172 /**
173 * Echo back the closing frame and close the connection
174 *
175 * @param channel
176 * Channel
177 * @param frame
178 * Web Socket frame that was received
179 */
180 @Override
181 public ChannelFuture close(Channel channel, CloseWebSocketFrame frame) {
182 ChannelFuture f = channel.write(frame);
183 f.addListener(ChannelFutureListener.CLOSE);
184 return f;
185 }
186
187 }