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 java.nio.channels.ClosedChannelException;
19 import java.util.Collections;
20 import java.util.LinkedHashSet;
21 import java.util.Set;
22
23 import io.netty.buffer.Unpooled;
24 import io.netty.channel.Channel;
25 import io.netty.channel.ChannelFuture;
26 import io.netty.channel.ChannelFutureListener;
27 import io.netty.channel.ChannelHandler;
28 import io.netty.channel.ChannelHandlerContext;
29 import io.netty.channel.ChannelInboundHandlerAdapter;
30 import io.netty.channel.ChannelOutboundInvoker;
31 import io.netty.channel.ChannelPipeline;
32 import io.netty.channel.ChannelPromise;
33 import io.netty.handler.codec.http.DefaultFullHttpRequest;
34 import io.netty.handler.codec.http.EmptyHttpHeaders;
35 import io.netty.handler.codec.http.FullHttpRequest;
36 import io.netty.handler.codec.http.FullHttpResponse;
37 import io.netty.handler.codec.http.HttpContentCompressor;
38 import io.netty.handler.codec.http.HttpHeaders;
39 import io.netty.handler.codec.http.HttpObject;
40 import io.netty.handler.codec.http.HttpObjectAggregator;
41 import io.netty.handler.codec.http.HttpRequest;
42 import io.netty.handler.codec.http.HttpRequestDecoder;
43 import io.netty.handler.codec.http.HttpResponseEncoder;
44 import io.netty.handler.codec.http.HttpServerCodec;
45 import io.netty.handler.codec.http.HttpUtil;
46 import io.netty.handler.codec.http.LastHttpContent;
47 import io.netty.util.ReferenceCountUtil;
48 import io.netty.util.internal.EmptyArrays;
49 import io.netty.util.internal.ObjectUtil;
50 import io.netty.util.internal.logging.InternalLogger;
51 import io.netty.util.internal.logging.InternalLoggerFactory;
52
53 /**
54 * Base class for server side web socket opening and closing handshakes
55 */
56 public abstract class WebSocketServerHandshaker {
57 protected static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker.class);
58
59 private final String uri;
60
61 private final String[] subprotocols;
62
63 private final WebSocketVersion version;
64
65 private final WebSocketDecoderConfig decoderConfig;
66
67 private String selectedSubprotocol;
68
69 /**
70 * Use this as wildcard to support all requested sub-protocols
71 */
72 public static final String SUB_PROTOCOL_WILDCARD = "*";
73
74 /**
75 * Constructor specifying the destination web socket location
76 *
77 * @param version
78 * the protocol version
79 * @param uri
80 * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
81 * sent to this URL.
82 * @param subprotocols
83 * CSV of supported protocols. Null if sub protocols not supported.
84 * @param maxFramePayloadLength
85 * Maximum length of a frame's payload
86 */
87 protected WebSocketServerHandshaker(
88 WebSocketVersion version, String uri, String subprotocols,
89 int maxFramePayloadLength) {
90 this(version, uri, subprotocols, WebSocketDecoderConfig.newBuilder()
91 .maxFramePayloadLength(maxFramePayloadLength)
92 .build());
93 }
94
95 /**
96 * Constructor specifying the destination web socket location
97 *
98 * @param version
99 * the protocol version
100 * @param uri
101 * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
102 * sent to this URL.
103 * @param subprotocols
104 * CSV of supported protocols. Null if sub protocols not supported.
105 * @param decoderConfig
106 * Frames decoder configuration.
107 */
108 protected WebSocketServerHandshaker(
109 WebSocketVersion version, String uri, String subprotocols, WebSocketDecoderConfig decoderConfig) {
110 this.version = version;
111 this.uri = uri;
112 if (subprotocols != null) {
113 String[] subprotocolArray = subprotocols.split(",");
114 for (int i = 0; i < subprotocolArray.length; i++) {
115 subprotocolArray[i] = subprotocolArray[i].trim();
116 }
117 this.subprotocols = subprotocolArray;
118 } else {
119 this.subprotocols = EmptyArrays.EMPTY_STRINGS;
120 }
121 this.decoderConfig = ObjectUtil.checkNotNull(decoderConfig, "decoderConfig");
122 }
123
124 /**
125 * Returns the URL of the web socket
126 */
127 @Deprecated
128 public String uri() {
129 return uri;
130 }
131
132 /**
133 * Returns the CSV of supported sub protocols
134 */
135 public Set<String> subprotocols() {
136 Set<String> ret = new LinkedHashSet<String>();
137 Collections.addAll(ret, subprotocols);
138 return ret;
139 }
140
141 /**
142 * Returns the version of the specification being supported
143 */
144 public WebSocketVersion version() {
145 return version;
146 }
147
148 /**
149 * Gets the maximum length for any frame's payload.
150 *
151 * @return The maximum length for a frame's payload
152 */
153 public int maxFramePayloadLength() {
154 return decoderConfig.maxFramePayloadLength();
155 }
156
157 /**
158 * Gets this decoder configuration.
159 *
160 * @return This decoder configuration.
161 */
162 public WebSocketDecoderConfig decoderConfig() {
163 return decoderConfig;
164 }
165
166 /**
167 * Performs the opening handshake. When call this method you <strong>MUST NOT</strong> retain the
168 * {@link FullHttpRequest} which is passed in.
169 *
170 * When called from within a {@link ChannelHandler} you most likely want to use
171 * {@link #handshake(ChannelHandlerContext, FullHttpRequest)}.
172 *
173 * @param channel
174 * Channel
175 * @param req
176 * HTTP Request
177 * @return future
178 * The {@link ChannelFuture} which is notified once the opening handshake completes
179 */
180 public ChannelFuture handshake(Channel channel, FullHttpRequest req) {
181 return handshake(channel, req, null, channel.newPromise());
182 }
183
184 /**
185 * Performs the opening handshake
186 *
187 * When call this method you <strong>MUST NOT</strong> retain the {@link FullHttpRequest} which is passed in.
188 *
189 * When called from within a {@link ChannelHandler} you most likely want to use
190 * {@link #handshake(ChannelHandlerContext, FullHttpRequest, HttpHeaders, ChannelPromise)}.
191 *
192 * @param channel
193 * Channel
194 * @param req
195 * HTTP Request
196 * @param responseHeaders
197 * Extra headers to add to the handshake response or {@code null} if no extra headers should be added
198 * @param promise
199 * the {@link ChannelPromise} to be notified when the opening handshake is done
200 * @return future
201 * the {@link ChannelFuture} which is notified when the opening handshake is done
202 */
203 public final ChannelFuture handshake(Channel channel, FullHttpRequest req,
204 HttpHeaders responseHeaders, final ChannelPromise promise) {
205 return handshake0(channel, channel, req, responseHeaders, promise);
206 }
207
208 /**
209 * Performs the opening handshake. When call this method you <strong>MUST NOT</strong> retain the
210 * {@link FullHttpRequest} which is passed in.
211 *
212 * The handshake response is written to the given {@link ChannelHandlerContext}, so handlers placed
213 * after the {@link ChannelHandler} the context belongs to will not see the response. The handler the
214 * context belongs to must be placed after the HTTP encoder as the response still needs to be encoded.
215 *
216 * @param ctx
217 * the {@link ChannelHandlerContext} to use.
218 * @param req
219 * HTTP Request
220 * @return future
221 * The {@link ChannelFuture} which is notified once the opening handshake completes
222 */
223 public ChannelFuture handshake(ChannelHandlerContext ctx, FullHttpRequest req) {
224 ObjectUtil.checkNotNull(ctx, "ctx");
225 return handshake(ctx, req, null, ctx.newPromise());
226 }
227
228 /**
229 * Performs the opening handshake
230 *
231 * When call this method you <strong>MUST NOT</strong> retain the {@link FullHttpRequest} which is passed in.
232 *
233 * The handshake response is written to the given {@link ChannelHandlerContext}, so handlers placed
234 * after the {@link ChannelHandler} the context belongs to will not see the response. The handler the
235 * context belongs to must be placed after the HTTP encoder as the response still needs to be encoded.
236 *
237 * @param ctx
238 * the {@link ChannelHandlerContext} to use.
239 * @param req
240 * HTTP Request
241 * @param responseHeaders
242 * Extra headers to add to the handshake response or {@code null} if no extra headers should be added
243 * @param promise
244 * the {@link ChannelPromise} to be notified when the opening handshake is done
245 * @return future
246 * the {@link ChannelFuture} which is notified when the opening handshake is done
247 */
248 public ChannelFuture handshake(ChannelHandlerContext ctx, FullHttpRequest req,
249 HttpHeaders responseHeaders, final ChannelPromise promise) {
250 ObjectUtil.checkNotNull(ctx, "ctx");
251 return handshake0(ctx, ctx.channel(), req, responseHeaders, promise);
252 }
253
254 private ChannelFuture handshake0(final ChannelOutboundInvoker invoker, final Channel channel, FullHttpRequest req,
255 HttpHeaders responseHeaders, final ChannelPromise promise) {
256
257 if (logger.isDebugEnabled()) {
258 logger.debug("{} WebSocket version {} server handshake", channel, version());
259 }
260 FullHttpResponse response = newHandshakeResponse(req, responseHeaders);
261 ChannelPipeline p = channel.pipeline();
262 if (p.get(HttpObjectAggregator.class) != null) {
263 p.remove(HttpObjectAggregator.class);
264 }
265 if (p.get(HttpContentCompressor.class) != null) {
266 p.remove(HttpContentCompressor.class);
267 }
268 ChannelHandlerContext ctx = p.context(HttpRequestDecoder.class);
269 final String encoderName;
270 if (ctx == null) {
271 // this means the user use an HttpServerCodec
272 ctx = p.context(HttpServerCodec.class);
273 if (ctx == null) {
274 promise.setFailure(
275 new IllegalStateException("No HttpDecoder and no HttpServerCodec in the pipeline"));
276 response.release();
277 return promise;
278 }
279 p.addBefore(ctx.name(), "wsencoder", newWebSocketEncoder());
280 p.addBefore(ctx.name(), "wsdecoder", newWebsocketDecoder());
281 encoderName = ctx.name();
282 } else {
283 p.replace(ctx.name(), "wsdecoder", newWebsocketDecoder());
284
285 encoderName = p.context(HttpResponseEncoder.class).name();
286 p.addBefore(encoderName, "wsencoder", newWebSocketEncoder());
287 }
288 invoker.writeAndFlush(response).addListener(future -> {
289 if (future.isSuccess()) {
290 ChannelPipeline p1 = channel.pipeline();
291 p1.remove(encoderName);
292 promise.setSuccess();
293 } else {
294 promise.setFailure(future.cause());
295 }
296 });
297 return promise;
298 }
299
300 /**
301 * Performs the opening handshake. When call this method you <strong>MUST NOT</strong> retain the
302 * {@link FullHttpRequest} which is passed in.
303 *
304 * When called from within a {@link ChannelHandler} you most likely want to use
305 * {@link #handshake(ChannelHandlerContext, HttpRequest)}.
306 *
307 * @param channel
308 * Channel
309 * @param req
310 * HTTP Request
311 * @return future
312 * The {@link ChannelFuture} which is notified once the opening handshake completes
313 */
314 public ChannelFuture handshake(Channel channel, HttpRequest req) {
315 return handshake(channel, req, null, channel.newPromise());
316 }
317
318 /**
319 * Performs the opening handshake
320 *
321 * When call this method you <strong>MUST NOT</strong> retain the {@link HttpRequest} which is passed in.
322 *
323 * When called from within a {@link ChannelHandler} you most likely want to use
324 * {@link #handshake(ChannelHandlerContext, HttpRequest, HttpHeaders, ChannelPromise)}.
325 *
326 * @param channel
327 * Channel
328 * @param req
329 * HTTP Request
330 * @param responseHeaders
331 * Extra headers to add to the handshake response or {@code null} if no extra headers should be added
332 * @param promise
333 * the {@link ChannelPromise} to be notified when the opening handshake is done
334 * @return future
335 * the {@link ChannelFuture} which is notified when the opening handshake is done
336 */
337 public final ChannelFuture handshake(final Channel channel, HttpRequest req,
338 final HttpHeaders responseHeaders, final ChannelPromise promise) {
339 return handshake0(channel, channel, req, responseHeaders, promise);
340 }
341
342 /**
343 * Performs the opening handshake. When call this method you <strong>MUST NOT</strong> retain the
344 * {@link HttpRequest} which is passed in.
345 *
346 * The handshake response is written to the given {@link ChannelHandlerContext}, so handlers placed
347 * after the {@link ChannelHandler} the context belongs to will not see the response. The handler the
348 * context belongs to must be placed after the HTTP encoder as the response still needs to be encoded.
349 *
350 * @param ctx
351 * the {@link ChannelHandlerContext} to use.
352 * @param req
353 * HTTP Request
354 * @return future
355 * The {@link ChannelFuture} which is notified once the opening handshake completes
356 */
357 public ChannelFuture handshake(ChannelHandlerContext ctx, HttpRequest req) {
358 ObjectUtil.checkNotNull(ctx, "ctx");
359 return handshake(ctx, req, null, ctx.newPromise());
360 }
361
362 /**
363 * Performs the opening handshake
364 *
365 * When call this method you <strong>MUST NOT</strong> retain the {@link HttpRequest} which is passed in.
366 *
367 * The handshake response is written to the given {@link ChannelHandlerContext}, so handlers placed
368 * after the {@link ChannelHandler} the context belongs to will not see the response. The handler the
369 * context belongs to must be placed after the HTTP encoder as the response still needs to be encoded.
370 *
371 * @param ctx
372 * the {@link ChannelHandlerContext} to use.
373 * @param req
374 * HTTP Request
375 * @param responseHeaders
376 * Extra headers to add to the handshake response or {@code null} if no extra headers should be added
377 * @param promise
378 * the {@link ChannelPromise} to be notified when the opening handshake is done
379 * @return future
380 * the {@link ChannelFuture} which is notified when the opening handshake is done
381 */
382 public ChannelFuture handshake(ChannelHandlerContext ctx, HttpRequest req,
383 final HttpHeaders responseHeaders, final ChannelPromise promise) {
384 ObjectUtil.checkNotNull(ctx, "ctx");
385 return handshake0(ctx, ctx.channel(), req, responseHeaders, promise);
386 }
387
388 private ChannelFuture handshake0(final ChannelOutboundInvoker invoker, final Channel channel, HttpRequest req,
389 final HttpHeaders responseHeaders, final ChannelPromise promise) {
390 if (req instanceof FullHttpRequest) {
391 return handshake0(invoker, channel, (FullHttpRequest) req, responseHeaders, promise);
392 }
393
394 if (logger.isDebugEnabled()) {
395 logger.debug("{} WebSocket version {} server handshake", channel, version());
396 }
397
398 ChannelPipeline p = channel.pipeline();
399 ChannelHandlerContext ctx = p.context(HttpRequestDecoder.class);
400 if (ctx == null) {
401 // this means the user use an HttpServerCodec
402 ctx = p.context(HttpServerCodec.class);
403 if (ctx == null) {
404 promise.setFailure(
405 new IllegalStateException("No HttpDecoder and no HttpServerCodec in the pipeline"));
406 return promise;
407 }
408 }
409
410 String aggregatorCtx = ctx.name();
411 if (HttpUtil.isContentLengthSet(req) || HttpUtil.isTransferEncodingChunked(req) ||
412 version == WebSocketVersion.V00) {
413 // Add aggregator and ensure we feed the HttpRequest so it is aggregated. A limit of 8192 should be
414 // more then enough for the websockets handshake payload.
415 aggregatorCtx = "httpAggregator";
416 p.addAfter(ctx.name(), aggregatorCtx, new HttpObjectAggregator(8192));
417 }
418
419 p.addAfter(aggregatorCtx, "handshaker", new ChannelInboundHandlerAdapter() {
420
421 private FullHttpRequest fullHttpRequest;
422
423 @Override
424 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
425 if (msg instanceof HttpObject) {
426 try {
427 handleHandshakeRequest(ctx, (HttpObject) msg);
428 } finally {
429 ReferenceCountUtil.release(msg);
430 }
431 } else {
432 super.channelRead(ctx, msg);
433 }
434 }
435
436 @Override
437 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
438 // Remove ourself and fail the handshake promise.
439 ctx.pipeline().remove(this);
440 promise.tryFailure(cause);
441 ctx.fireExceptionCaught(cause);
442 }
443
444 @Override
445 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
446 try {
447 // Fail promise if Channel was closed
448 if (!promise.isDone()) {
449 promise.tryFailure(new ClosedChannelException());
450 }
451 ctx.fireChannelInactive();
452 } finally {
453 releaseFullHttpRequest();
454 }
455 }
456
457 @Override
458 public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
459 releaseFullHttpRequest();
460 }
461
462 private void handleHandshakeRequest(ChannelHandlerContext ctx, HttpObject httpObject) {
463 if (httpObject instanceof FullHttpRequest) {
464 ctx.pipeline().remove(this);
465 handshake0(invoker, channel, (FullHttpRequest) httpObject, responseHeaders, promise);
466 return;
467 }
468
469 if (httpObject instanceof LastHttpContent) {
470 assert fullHttpRequest != null;
471 FullHttpRequest handshakeRequest = fullHttpRequest;
472 fullHttpRequest = null;
473 try {
474 ctx.pipeline().remove(this);
475 handshake0(invoker, channel, handshakeRequest, responseHeaders, promise);
476 } finally {
477 handshakeRequest.release();
478 }
479 return;
480 }
481
482 if (httpObject instanceof HttpRequest) {
483 HttpRequest httpRequest = (HttpRequest) httpObject;
484 fullHttpRequest = new DefaultFullHttpRequest(httpRequest.protocolVersion(), httpRequest.method(),
485 httpRequest.uri(), Unpooled.EMPTY_BUFFER, httpRequest.headers(), EmptyHttpHeaders.INSTANCE);
486 if (httpRequest.decoderResult().isFailure()) {
487 fullHttpRequest.setDecoderResult(httpRequest.decoderResult());
488 }
489 }
490 }
491
492 private void releaseFullHttpRequest() {
493 if (fullHttpRequest != null) {
494 fullHttpRequest.release();
495 fullHttpRequest = null;
496 }
497 }
498 });
499 try {
500 ctx.fireChannelRead(ReferenceCountUtil.retain(req));
501 } catch (Throwable cause) {
502 promise.setFailure(cause);
503 }
504 return promise;
505 }
506
507 /**
508 * Returns a new {@link FullHttpResponse) which will be used for as response to the handshake request.
509 */
510 protected abstract FullHttpResponse newHandshakeResponse(FullHttpRequest req,
511 HttpHeaders responseHeaders);
512 /**
513 * Performs the closing handshake.
514 *
515 * When called from within a {@link ChannelHandler} you most likely want to use
516 * {@link #close(ChannelHandlerContext, CloseWebSocketFrame)}.
517 *
518 * @param channel
519 * the {@link Channel} to use.
520 * @param frame
521 * Closing Frame that was received.
522 */
523 public ChannelFuture close(Channel channel, CloseWebSocketFrame frame) {
524 ObjectUtil.checkNotNull(channel, "channel");
525 return close(channel, frame, channel.newPromise());
526 }
527
528 /**
529 * Performs the closing handshake.
530 *
531 * When called from within a {@link ChannelHandler} you most likely want to use
532 * {@link #close(ChannelHandlerContext, CloseWebSocketFrame, ChannelPromise)}.
533 *
534 * @param channel
535 * the {@link Channel} to use.
536 * @param frame
537 * Closing Frame that was received.
538 * @param promise
539 * the {@link ChannelPromise} to be notified when the closing handshake is done
540 */
541 public ChannelFuture close(Channel channel, CloseWebSocketFrame frame, ChannelPromise promise) {
542 return close0(channel, frame, promise);
543 }
544
545 /**
546 * Performs the closing handshake.
547 *
548 * @param ctx
549 * the {@link ChannelHandlerContext} to use.
550 * @param frame
551 * Closing Frame that was received.
552 */
553 public ChannelFuture close(ChannelHandlerContext ctx, CloseWebSocketFrame frame) {
554 ObjectUtil.checkNotNull(ctx, "ctx");
555 return close(ctx, frame, ctx.newPromise());
556 }
557
558 /**
559 * Performs the closing handshake.
560 *
561 * @param ctx
562 * the {@link ChannelHandlerContext} to use.
563 * @param frame
564 * Closing Frame that was received.
565 * @param promise
566 * the {@link ChannelPromise} to be notified when the closing handshake is done.
567 */
568 public ChannelFuture close(ChannelHandlerContext ctx, CloseWebSocketFrame frame, ChannelPromise promise) {
569 ObjectUtil.checkNotNull(ctx, "ctx");
570 return close0(ctx, frame, promise).addListener(ChannelFutureListener.CLOSE);
571 }
572
573 private ChannelFuture close0(ChannelOutboundInvoker invoker, CloseWebSocketFrame frame, ChannelPromise promise) {
574 return invoker.writeAndFlush(frame, promise).addListener(ChannelFutureListener.CLOSE);
575 }
576
577 /**
578 * Selects the first matching supported sub protocol
579 *
580 * @param requestedSubprotocols
581 * CSV of protocols to be supported. e.g. "chat, superchat"
582 * @return First matching supported sub protocol. Null if not found.
583 */
584 protected String selectSubprotocol(String requestedSubprotocols) {
585 if (requestedSubprotocols == null || subprotocols.length == 0) {
586 return null;
587 }
588
589 String[] requestedSubprotocolArray = requestedSubprotocols.split(",");
590 for (String p: requestedSubprotocolArray) {
591 String requestedSubprotocol = p.trim();
592
593 for (String supportedSubprotocol: subprotocols) {
594 if (SUB_PROTOCOL_WILDCARD.equals(supportedSubprotocol)
595 || requestedSubprotocol.equals(supportedSubprotocol)) {
596 selectedSubprotocol = requestedSubprotocol;
597 return requestedSubprotocol;
598 }
599 }
600 }
601
602 // No match found
603 return null;
604 }
605
606 /**
607 * Returns the selected subprotocol. Null if no subprotocol has been selected.
608 * <p>
609 * This is only available AFTER <tt>handshake()</tt> has been called.
610 * </p>
611 */
612 public String selectedSubprotocol() {
613 return selectedSubprotocol;
614 }
615
616 /**
617 * Returns the decoder to use after handshake is complete.
618 */
619 protected abstract WebSocketFrameDecoder newWebsocketDecoder();
620
621 /**
622 * Returns the encoder to use after the handshake is complete.
623 */
624 protected abstract WebSocketFrameEncoder newWebSocketEncoder();
625
626 }