1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty5.handler.codec.http.websocketx;
17
18 import io.netty5.buffer.api.BufferAllocator;
19 import io.netty5.handler.codec.http.DefaultFullHttpRequest;
20 import io.netty5.handler.codec.http.FullHttpRequest;
21 import io.netty5.handler.codec.http.FullHttpResponse;
22 import io.netty5.handler.codec.http.HttpHeaderNames;
23 import io.netty5.handler.codec.http.HttpHeaderValues;
24 import io.netty5.handler.codec.http.HttpHeaders;
25 import io.netty5.handler.codec.http.HttpMethod;
26 import io.netty5.handler.codec.http.HttpResponseStatus;
27 import io.netty5.handler.codec.http.HttpVersion;
28 import io.netty5.util.internal.StringUtil;
29
30 import java.net.URI;
31
32
33
34
35
36
37
38 public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
39
40 private final boolean allowExtensions;
41 private final boolean performMasking;
42 private final boolean allowMaskMismatch;
43
44 private volatile String sentNonce;
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 public WebSocketClientHandshaker13(URI webSocketURL, String subprotocol,
62 boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength) {
63 this(webSocketURL, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength,
64 true, false);
65 }
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89 public WebSocketClientHandshaker13(URI webSocketURL, String subprotocol,
90 boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
91 boolean performMasking, boolean allowMaskMismatch) {
92 this(webSocketURL, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength,
93 performMasking, allowMaskMismatch, DEFAULT_FORCE_CLOSE_TIMEOUT_MILLIS);
94 }
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 public WebSocketClientHandshaker13(URI webSocketURL, String subprotocol,
121 boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
122 boolean performMasking, boolean allowMaskMismatch,
123 long forceCloseTimeoutMillis) {
124 this(webSocketURL, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, performMasking,
125 allowMaskMismatch, forceCloseTimeoutMillis, false);
126 }
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155 WebSocketClientHandshaker13(URI webSocketURL, String subprotocol,
156 boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
157 boolean performMasking, boolean allowMaskMismatch,
158 long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl) {
159 super(webSocketURL, WebSocketVersion.V13, subprotocol, customHeaders, maxFramePayloadLength,
160 forceCloseTimeoutMillis, absoluteUpgradeUrl);
161 this.allowExtensions = allowExtensions;
162 this.performMasking = performMasking;
163 this.allowMaskMismatch = allowMaskMismatch;
164 }
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183 @Override
184 protected FullHttpRequest newHandshakeRequest(BufferAllocator allocator) {
185 URI wsURL = uri();
186 FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, upgradeUrl(wsURL),
187 allocator.allocate(0));
188 HttpHeaders headers = request.headers();
189
190 if (customHeaders != null) {
191 headers.add(customHeaders);
192 if (!headers.contains(HttpHeaderNames.HOST)) {
193
194
195 headers.set(HttpHeaderNames.HOST, websocketHostValue(wsURL));
196 }
197 } else {
198 headers.set(HttpHeaderNames.HOST, websocketHostValue(wsURL));
199 }
200
201 String nonce = createNonce();
202 headers.set(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET)
203 .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE)
204 .set(HttpHeaderNames.SEC_WEBSOCKET_KEY, nonce);
205
206 sentNonce = nonce;
207 String expectedSubprotocol = expectedSubprotocol();
208 if (!StringUtil.isNullOrEmpty(expectedSubprotocol)) {
209 headers.set(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL, expectedSubprotocol);
210 }
211
212 headers.set(HttpHeaderNames.SEC_WEBSOCKET_VERSION, version().toAsciiString());
213 return request;
214 }
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233 @Override
234 protected void verify(FullHttpResponse response) {
235 HttpResponseStatus status = response.status();
236 if (!HttpResponseStatus.SWITCHING_PROTOCOLS.equals(status)) {
237 throw new WebSocketClientHandshakeException("Invalid handshake response status: " + status, response);
238 }
239
240 HttpHeaders headers = response.headers();
241 CharSequence upgrade = headers.get(HttpHeaderNames.UPGRADE);
242 if (!HttpHeaderValues.WEBSOCKET.contentEqualsIgnoreCase(upgrade)) {
243 throw new WebSocketClientHandshakeException("Invalid handshake response upgrade: " + upgrade, response);
244 }
245
246 if (!headers.containsValue(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE, true)) {
247 throw new WebSocketClientHandshakeException("Invalid handshake response connection: "
248 + headers.get(HttpHeaderNames.CONNECTION), response);
249 }
250
251 String accept = headers.get(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT);
252 if (accept == null) {
253 throw new WebSocketClientHandshakeException("Invalid handshake response sec-websocket-accept: null",
254 response);
255 }
256
257 String expectedAccept = WebSocketUtil.calculateV13Accept(sentNonce);
258 if (!expectedAccept.equals(accept.trim())) {
259 throw new WebSocketClientHandshakeException("Invalid handshake response sec-websocket-accept: " + accept +
260 ", expected: " + expectedAccept, response);
261 }
262 }
263
264 @Override
265 protected WebSocketFrameDecoder newWebsocketDecoder() {
266 return new WebSocket13FrameDecoder(false, allowExtensions, maxFramePayloadLength(), allowMaskMismatch);
267 }
268
269 @Override
270 protected WebSocketFrameEncoder newWebSocketEncoder() {
271 return new WebSocket13FrameEncoder(performMasking);
272 }
273
274 @Override
275 public WebSocketClientHandshaker13 setForceCloseTimeoutMillis(long forceCloseTimeoutMillis) {
276 super.setForceCloseTimeoutMillis(forceCloseTimeoutMillis);
277 return this;
278 }
279
280
281
282
283
284 private static String createNonce() {
285 var nonce = WebSocketUtil.randomBytes(16);
286 return WebSocketUtil.base64(nonce);
287 }
288 }