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