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