1 /*
2 * Copyright 2019 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 * https://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 io.netty.handler.codec.http.websocketx;
17
18 import io.netty.handler.codec.http.DefaultFullHttpResponse;
19 import io.netty.handler.codec.http.FullHttpRequest;
20 import io.netty.handler.codec.http.FullHttpResponse;
21 import io.netty.handler.codec.http.HttpHeaderNames;
22 import io.netty.handler.codec.http.HttpHeaderValues;
23 import io.netty.handler.codec.http.HttpHeaders;
24 import io.netty.handler.codec.http.HttpMethod;
25 import io.netty.handler.codec.http.HttpResponseStatus;
26 import io.netty.util.CharsetUtil;
27
28 import static io.netty.handler.codec.http.HttpMethod.GET;
29 import static io.netty.handler.codec.http.HttpVersion.*;
30
31 /**
32 * <p>
33 * Performs server side opening and closing handshakes for <a href="https://netty.io/s/rfc6455">RFC 6455</a>
34 * (originally web socket specification <a href="https://netty.io/s/ws-17">draft-ietf-hybi-thewebsocketprotocol-17</a>).
35 * </p>
36 */
37 public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
38
39 public static final String WEBSOCKET_13_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
40
41 /**
42 * Constructor specifying the destination web socket location
43 *
44 * @param webSocketURL
45 * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web
46 * socket frames will be sent to this URL.
47 * @param subprotocols
48 * CSV of supported protocols
49 * @param allowExtensions
50 * Allow extensions to be used in the reserved bits of the web socket frame
51 * @param maxFramePayloadLength
52 * Maximum allowable frame payload length. Setting this value to your application's
53 * requirement may reduce denial of service attacks using long data frames.
54 */
55 public WebSocketServerHandshaker13(
56 String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength) {
57 this(webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, false);
58 }
59
60 /**
61 * Constructor specifying the destination web socket location
62 *
63 * @param webSocketURL
64 * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web
65 * socket frames will be sent to this URL.
66 * @param subprotocols
67 * CSV of supported protocols
68 * @param allowExtensions
69 * Allow extensions to be used in the reserved bits of the web socket frame
70 * @param maxFramePayloadLength
71 * Maximum allowable frame payload length. Setting this value to your application's
72 * requirement may reduce denial of service attacks using long data frames.
73 * @param allowMaskMismatch
74 * When set to true, frames which are not masked properly according to the standard will still be
75 * accepted.
76 */
77 public WebSocketServerHandshaker13(
78 String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength,
79 boolean allowMaskMismatch) {
80 this(webSocketURL, subprotocols, WebSocketDecoderConfig.newBuilder()
81 .allowExtensions(allowExtensions)
82 .maxFramePayloadLength(maxFramePayloadLength)
83 .allowMaskMismatch(allowMaskMismatch)
84 .build());
85 }
86
87 /**
88 * Constructor specifying the destination web socket location
89 *
90 * @param webSocketURL
91 * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web
92 * socket frames will be sent to this URL.
93 * @param subprotocols
94 * CSV of supported protocols
95 * @param decoderConfig
96 * Frames decoder configuration.
97 */
98 public WebSocketServerHandshaker13(
99 String webSocketURL, String subprotocols, WebSocketDecoderConfig decoderConfig) {
100 super(WebSocketVersion.V13, webSocketURL, subprotocols, decoderConfig);
101 }
102
103 /**
104 * <p>
105 * Handle the web socket handshake for the web socket specification <a href=
106 * "https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">HyBi versions 13-17</a>. Versions 13-17
107 * share the same wire protocol.
108 * </p>
109 *
110 * <p>
111 * Browser request to the server:
112 * </p>
113 *
114 * <pre>
115 * GET /chat HTTP/1.1
116 * Host: server.example.com
117 * Upgrade: websocket
118 * Connection: Upgrade
119 * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
120 * Origin: http://example.com
121 * Sec-WebSocket-Protocol: chat, superchat
122 * Sec-WebSocket-Version: 13
123 * </pre>
124 *
125 * <p>
126 * Server response:
127 * </p>
128 *
129 * <pre>
130 * HTTP/1.1 101 Switching Protocols
131 * Upgrade: websocket
132 * Connection: Upgrade
133 * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
134 * Sec-WebSocket-Protocol: chat
135 * </pre>
136 */
137 @Override
138 protected FullHttpResponse newHandshakeResponse(FullHttpRequest req, HttpHeaders headers) {
139 HttpMethod method = req.method();
140 if (!GET.equals(method)) {
141 throw new WebSocketServerHandshakeException("Invalid WebSocket handshake method: " + method, req);
142 }
143
144 HttpHeaders reqHeaders = req.headers();
145 if (!reqHeaders.contains(HttpHeaderNames.CONNECTION) ||
146 !reqHeaders.containsValue(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE, true)) {
147 throw new WebSocketServerHandshakeException(
148 "not a WebSocket request: a |Connection| header must includes a token 'Upgrade'", req);
149 }
150
151 if (!reqHeaders.contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true)) {
152 throw new WebSocketServerHandshakeException(
153 "not a WebSocket request: a |Upgrade| header must containing the value 'websocket'", req);
154 }
155
156 CharSequence key = reqHeaders.get(HttpHeaderNames.SEC_WEBSOCKET_KEY);
157 if (key == null) {
158 throw new WebSocketServerHandshakeException("not a WebSocket request: missing key", req);
159 }
160
161 FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS,
162 req.content().alloc().buffer(0));
163 if (headers != null) {
164 res.headers().add(headers);
165 }
166
167 String acceptSeed = key + WEBSOCKET_13_ACCEPT_GUID;
168 byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
169 String accept = WebSocketUtil.base64(sha1);
170
171 if (logger.isDebugEnabled()) {
172 logger.debug("WebSocket version 13 server handshake key: {}, response: {}", key, accept);
173 }
174
175 res.headers().set(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET)
176 .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE)
177 .set(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT, accept);
178
179 String subprotocols = reqHeaders.get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL);
180 if (subprotocols != null) {
181 String selectedSubprotocol = selectSubprotocol(subprotocols);
182 if (selectedSubprotocol == null) {
183 if (logger.isDebugEnabled()) {
184 logger.debug("Requested subprotocol(s) not supported: {}", subprotocols);
185 }
186 } else {
187 res.headers().set(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol);
188 }
189 }
190 return res;
191 }
192
193 @Override
194 protected WebSocketFrameDecoder newWebsocketDecoder() {
195 return new WebSocket13FrameDecoder(decoderConfig());
196 }
197
198 @Override
199 protected WebSocketFrameEncoder newWebSocketEncoder() {
200 return new WebSocket13FrameEncoder(false);
201 }
202 }