View Javadoc
1   /*
2    * Copyright 2014 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.extensions;
17  
18  import io.netty.channel.ChannelFuture;
19  import io.netty.channel.ChannelFutureListener;
20  import io.netty.channel.ChannelHandlerAdapter;
21  import io.netty.channel.ChannelHandlerContext;
22  import io.netty.channel.ChannelPromise;
23  import io.netty.handler.codec.http.HttpHeaderNames;
24  import io.netty.handler.codec.http.HttpRequest;
25  import io.netty.handler.codec.http.HttpResponse;
26  
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Iterator;
30  import java.util.List;
31  
32  /**
33   * This handler negotiates and initializes the WebSocket Extensions.
34   *
35   * It negotiates the extensions based on the client desired order,
36   * ensures that the successfully negotiated extensions are consistent between them,
37   * and initializes the channel pipeline with the extension decoder and encoder.
38   *
39   * Find a basic implementation for compression extensions at
40   * <tt>io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler</tt>.
41   */
42  public class WebSocketServerExtensionHandler extends ChannelHandlerAdapter {
43  
44      private final List<WebSocketServerExtensionHandshaker> extensionHandshakers;
45  
46      private List<WebSocketServerExtension> validExtensions;
47  
48      /**
49       * Constructor
50       *
51       * @param extensionHandshakers
52       *      The extension handshaker in priority order. A handshaker could be repeated many times
53       *      with fallback configuration.
54       */
55      public WebSocketServerExtensionHandler(WebSocketServerExtensionHandshaker... extensionHandshakers) {
56          if (extensionHandshakers == null) {
57              throw new NullPointerException("extensionHandshakers");
58          }
59          if (extensionHandshakers.length == 0) {
60              throw new IllegalArgumentException("extensionHandshakers must contains at least one handshaker");
61          }
62          this.extensionHandshakers = Arrays.asList(extensionHandshakers);
63      }
64  
65      @Override
66      public void channelRead(ChannelHandlerContext ctx, Object msg)
67              throws Exception {
68          if (msg instanceof HttpRequest) {
69              HttpRequest request = (HttpRequest) msg;
70  
71              if (WebSocketExtensionUtil.isWebsocketUpgrade(request)) {
72                  String extensionsHeader = request.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS);
73  
74                  if (extensionsHeader != null) {
75                      List<WebSocketExtensionData> extensions =
76                              WebSocketExtensionUtil.extractExtensions(extensionsHeader);
77                      int rsv = 0;
78  
79                      for (WebSocketExtensionData extensionData : extensions) {
80                          Iterator<WebSocketServerExtensionHandshaker> extensionHandshakersIterator =
81                                  extensionHandshakers.iterator();
82                          WebSocketServerExtension validExtension = null;
83  
84                          while (validExtension == null && extensionHandshakersIterator.hasNext()) {
85                              WebSocketServerExtensionHandshaker extensionHandshaker =
86                                      extensionHandshakersIterator.next();
87                              validExtension = extensionHandshaker.handshakeExtension(extensionData);
88                          }
89  
90                          if (validExtension != null && ((validExtension.rsv() & rsv) == 0)) {
91                              if (validExtensions == null) {
92                                  validExtensions = new ArrayList<WebSocketServerExtension>(1);
93                              }
94                              rsv = rsv | validExtension.rsv();
95                              validExtensions.add(validExtension);
96                          }
97                      }
98                  }
99              }
100         }
101 
102         super.channelRead(ctx, msg);
103     }
104 
105     @Override
106     public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
107         if (msg instanceof HttpResponse &&
108                 WebSocketExtensionUtil.isWebsocketUpgrade((HttpResponse) msg) && validExtensions != null) {
109             HttpResponse response = (HttpResponse) msg;
110             String headerValue = response.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS);
111 
112             for (WebSocketServerExtension extension : validExtensions) {
113                 WebSocketExtensionData extensionData = extension.newReponseData();
114                 headerValue = WebSocketExtensionUtil.appendExtension(headerValue,
115                         extensionData.name(), extensionData.parameters());
116             }
117 
118             promise.addListener(new ChannelFutureListener() {
119                 @Override
120                 public void operationComplete(ChannelFuture future) throws Exception {
121                     if (future.isSuccess()) {
122                         for (WebSocketServerExtension extension : validExtensions) {
123                             WebSocketExtensionDecoder decoder = extension.newExtensionDecoder();
124                             WebSocketExtensionEncoder encoder = extension.newExtensionEncoder();
125                             ctx.pipeline().addAfter(ctx.name(), decoder.getClass().getName(), decoder);
126                             ctx.pipeline().addAfter(ctx.name(), encoder.getClass().getName(), encoder);
127                         }
128                     }
129 
130                     ctx.pipeline().remove(ctx.name());
131                 }
132             });
133 
134             if (headerValue != null) {
135                 response.headers().set(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS, headerValue);
136             }
137         }
138 
139         super.write(ctx, msg, promise);
140     }
141 
142 }