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    *   http://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.buffer.Unpooled;
19  import io.netty.channel.Channel;
20  import io.netty.channel.ChannelFutureListener;
21  import io.netty.channel.ChannelHandlerContext;
22  import io.netty.channel.ChannelInboundHandler;
23  import io.netty.channel.ChannelPipeline;
24  import io.netty.handler.codec.http.DefaultFullHttpResponse;
25  import io.netty.handler.codec.http.FullHttpResponse;
26  import io.netty.handler.codec.http.HttpHeaders;
27  import io.netty.handler.codec.http.HttpResponseStatus;
28  import io.netty.util.AttributeKey;
29  
30  import java.util.List;
31  
32  import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
33  import static io.netty.handler.codec.http.websocketx.WebSocketServerProtocolConfig.DEFAULT_HANDSHAKE_TIMEOUT_MILLIS;
34  import static io.netty.util.internal.ObjectUtil.checkNotNull;
35  
36  /**
37   * This handler does all the heavy lifting for you to run a websocket server.
38   *
39   * It takes care of websocket handshaking as well as processing of control frames (Close, Ping, Pong). Text and Binary
40   * data frames are passed to the next handler in the pipeline (implemented by you) for processing.
41   *
42   * See <tt>io.netty.example.http.websocketx.html5.WebSocketServer</tt> for usage.
43   *
44   * The implementation of this handler assumes that you just want to run  a websocket server and not process other types
45   * HTTP requests (like GET and POST). If you wish to support both HTTP requests and websockets in the one server, refer
46   * to the <tt>io.netty.example.http.websocketx.server.WebSocketServer</tt> example.
47   *
48   * To know once a handshake was done you can intercept the
49   * {@link ChannelInboundHandler#userEventTriggered(ChannelHandlerContext, Object)} and check if the event was instance
50   * of {@link HandshakeComplete}, the event will contain extra information about the handshake such as the request and
51   * selected subprotocol.
52   */
53  public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler {
54  
55      /**
56       * Events that are fired to notify about handshake status
57       */
58      public enum ServerHandshakeStateEvent {
59          /**
60           * The Handshake was completed successfully and the channel was upgraded to websockets.
61           *
62           * @deprecated in favor of {@link HandshakeComplete} class,
63           * it provides extra information about the handshake
64           */
65          @Deprecated
66          HANDSHAKE_COMPLETE,
67  
68          /**
69           * The Handshake was timed out
70           */
71          HANDSHAKE_TIMEOUT
72      }
73  
74      /**
75       * The Handshake was completed successfully and the channel was upgraded to websockets.
76       */
77      public static final class HandshakeComplete {
78          private final String requestUri;
79          private final HttpHeaders requestHeaders;
80          private final String selectedSubprotocol;
81  
82          HandshakeComplete(String requestUri, HttpHeaders requestHeaders, String selectedSubprotocol) {
83              this.requestUri = requestUri;
84              this.requestHeaders = requestHeaders;
85              this.selectedSubprotocol = selectedSubprotocol;
86          }
87  
88          public String requestUri() {
89              return requestUri;
90          }
91  
92          public HttpHeaders requestHeaders() {
93              return requestHeaders;
94          }
95  
96          public String selectedSubprotocol() {
97              return selectedSubprotocol;
98          }
99      }
100 
101     private static final AttributeKey<WebSocketServerHandshaker> HANDSHAKER_ATTR_KEY =
102             AttributeKey.valueOf(WebSocketServerHandshaker.class, "HANDSHAKER");
103 
104     private final WebSocketServerProtocolConfig serverConfig;
105 
106     /**
107      * Base constructor
108      *
109      * @param serverConfig
110      *            Server protocol configuration.
111      */
112     public WebSocketServerProtocolHandler(WebSocketServerProtocolConfig serverConfig) {
113         super(checkNotNull(serverConfig, "serverConfig").dropPongFrames(),
114               serverConfig.sendCloseFrame(),
115               serverConfig.forceCloseTimeoutMillis()
116         );
117         this.serverConfig = serverConfig;
118     }
119 
120     public WebSocketServerProtocolHandler(String websocketPath) {
121         this(websocketPath, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
122     }
123 
124     public WebSocketServerProtocolHandler(String websocketPath, long handshakeTimeoutMillis) {
125         this(websocketPath, false, handshakeTimeoutMillis);
126     }
127 
128     public WebSocketServerProtocolHandler(String websocketPath, boolean checkStartsWith) {
129         this(websocketPath, checkStartsWith, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
130     }
131 
132     public WebSocketServerProtocolHandler(String websocketPath, boolean checkStartsWith, long handshakeTimeoutMillis) {
133         this(websocketPath, null, false, 65536, false, checkStartsWith, handshakeTimeoutMillis);
134     }
135 
136     public WebSocketServerProtocolHandler(String websocketPath, String subprotocols) {
137         this(websocketPath, subprotocols, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
138     }
139 
140     public WebSocketServerProtocolHandler(String websocketPath, String subprotocols, long handshakeTimeoutMillis) {
141         this(websocketPath, subprotocols, false, handshakeTimeoutMillis);
142     }
143 
144     public WebSocketServerProtocolHandler(String websocketPath, String subprotocols, boolean allowExtensions) {
145         this(websocketPath, subprotocols, allowExtensions, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
146     }
147 
148     public WebSocketServerProtocolHandler(String websocketPath, String subprotocols, boolean allowExtensions,
149                                           long handshakeTimeoutMillis) {
150         this(websocketPath, subprotocols, allowExtensions, 65536, handshakeTimeoutMillis);
151     }
152 
153     public WebSocketServerProtocolHandler(String websocketPath, String subprotocols,
154                                           boolean allowExtensions, int maxFrameSize) {
155         this(websocketPath, subprotocols, allowExtensions, maxFrameSize, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
156     }
157 
158     public WebSocketServerProtocolHandler(String websocketPath, String subprotocols,
159                                           boolean allowExtensions, int maxFrameSize, long handshakeTimeoutMillis) {
160         this(websocketPath, subprotocols, allowExtensions, maxFrameSize, false, handshakeTimeoutMillis);
161     }
162 
163     public WebSocketServerProtocolHandler(String websocketPath, String subprotocols,
164             boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch) {
165         this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch,
166              DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
167     }
168 
169     public WebSocketServerProtocolHandler(String websocketPath, String subprotocols, boolean allowExtensions,
170                                           int maxFrameSize, boolean allowMaskMismatch, long handshakeTimeoutMillis) {
171         this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch, false,
172              handshakeTimeoutMillis);
173     }
174 
175     public WebSocketServerProtocolHandler(String websocketPath, String subprotocols,
176             boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch, boolean checkStartsWith) {
177         this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch, checkStartsWith,
178              DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
179     }
180 
181     public WebSocketServerProtocolHandler(String websocketPath, String subprotocols,
182                                           boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch,
183                                           boolean checkStartsWith, long handshakeTimeoutMillis) {
184         this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch, checkStartsWith, true,
185              handshakeTimeoutMillis);
186     }
187 
188     public WebSocketServerProtocolHandler(String websocketPath, String subprotocols,
189                                           boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch,
190                                           boolean checkStartsWith, boolean dropPongFrames) {
191         this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch, checkStartsWith,
192              dropPongFrames, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
193     }
194 
195     public WebSocketServerProtocolHandler(String websocketPath, String subprotocols, boolean allowExtensions,
196                                           int maxFrameSize, boolean allowMaskMismatch, boolean checkStartsWith,
197                                           boolean dropPongFrames, long handshakeTimeoutMillis) {
198         this(websocketPath, subprotocols, checkStartsWith, dropPongFrames, handshakeTimeoutMillis,
199             WebSocketDecoderConfig.newBuilder()
200                 .maxFramePayloadLength(maxFrameSize)
201                 .allowMaskMismatch(allowMaskMismatch)
202                 .allowExtensions(allowExtensions)
203                 .build());
204     }
205 
206     public WebSocketServerProtocolHandler(String websocketPath, String subprotocols, boolean checkStartsWith,
207                                           boolean dropPongFrames, long handshakeTimeoutMillis,
208                                           WebSocketDecoderConfig decoderConfig) {
209         this(WebSocketServerProtocolConfig.newBuilder()
210             .websocketPath(websocketPath)
211             .subprotocols(subprotocols)
212             .checkStartsWith(checkStartsWith)
213             .handshakeTimeoutMillis(handshakeTimeoutMillis)
214             .dropPongFrames(dropPongFrames)
215             .decoderConfig(decoderConfig)
216             .build());
217     }
218 
219     @Override
220     public void handlerAdded(ChannelHandlerContext ctx) {
221         ChannelPipeline cp = ctx.pipeline();
222         if (cp.get(WebSocketServerProtocolHandshakeHandler.class) == null) {
223             // Add the WebSocketHandshakeHandler before this one.
224             cp.addBefore(ctx.name(), WebSocketServerProtocolHandshakeHandler.class.getName(),
225                     new WebSocketServerProtocolHandshakeHandler(serverConfig));
226         }
227         if (serverConfig.decoderConfig().withUTF8Validator() && cp.get(Utf8FrameValidator.class) == null) {
228             // Add the UFT8 checking before this one.
229             cp.addBefore(ctx.name(), Utf8FrameValidator.class.getName(),
230                     new Utf8FrameValidator());
231         }
232     }
233 
234     @Override
235     protected void decode(ChannelHandlerContext ctx, WebSocketFrame frame, List<Object> out) throws Exception {
236         if (serverConfig.handleCloseFrames() && frame instanceof CloseWebSocketFrame) {
237             WebSocketServerHandshaker handshaker = getHandshaker(ctx.channel());
238             if (handshaker != null) {
239                 frame.retain();
240                 handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame);
241             } else {
242                 ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
243             }
244             return;
245         }
246         super.decode(ctx, frame, out);
247     }
248 
249     @Override
250     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
251         if (cause instanceof WebSocketHandshakeException) {
252             FullHttpResponse response = new DefaultFullHttpResponse(
253                     HTTP_1_1, HttpResponseStatus.BAD_REQUEST, Unpooled.wrappedBuffer(cause.getMessage().getBytes()));
254             ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
255         } else {
256             ctx.fireExceptionCaught(cause);
257             ctx.close();
258         }
259     }
260 
261     static WebSocketServerHandshaker getHandshaker(Channel channel) {
262         return channel.attr(HANDSHAKER_ATTR_KEY).get();
263     }
264 
265     static void setHandshaker(Channel channel, WebSocketServerHandshaker handshaker) {
266         channel.attr(HANDSHAKER_ATTR_KEY).set(handshaker);
267     }
268 }