View Javadoc
1   /*
2    * Copyright 2013 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.ByteBuf;
19  import io.netty.buffer.CompositeByteBuf;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.handler.codec.MessageToMessageDecoder;
22  import io.netty.handler.codec.TooLongFrameException;
23  
24  import java.util.List;
25  
26  /**
27   * Handler that aggregate fragmented WebSocketFrame's.
28   *
29   * Be aware if PING/PONG/CLOSE frames are send in the middle of a fragmented {@link WebSocketFrame} they will
30   * just get forwarded to the next handler in the pipeline.
31   */
32  public class WebSocketFrameAggregator extends MessageToMessageDecoder<WebSocketFrame> {
33      private final int maxFrameSize;
34      private WebSocketFrame currentFrame;
35      private boolean tooLongFrameFound;
36  
37      /**
38       * Construct a new instance
39       *
40       * @param maxFrameSize      If the size of the aggregated frame exceeds this value,
41       *                          a {@link TooLongFrameException} is thrown.
42       */
43      public WebSocketFrameAggregator(int maxFrameSize) {
44          if (maxFrameSize < 1) {
45              throw new IllegalArgumentException("maxFrameSize must be > 0");
46          }
47          this.maxFrameSize = maxFrameSize;
48      }
49  
50      @Override
51      protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) throws Exception {
52          if (currentFrame == null) {
53              tooLongFrameFound = false;
54              if (msg.isFinalFragment()) {
55                  out.add(msg.retain());
56                  return;
57              }
58              ByteBuf buf = ctx.alloc().compositeBuffer().addComponent(msg.content().retain());
59              buf.writerIndex(buf.writerIndex() + msg.content().readableBytes());
60  
61              if (msg instanceof TextWebSocketFrame) {
62                  currentFrame = new TextWebSocketFrame(true, msg.rsv(), buf);
63              } else if (msg instanceof BinaryWebSocketFrame) {
64                  currentFrame = new BinaryWebSocketFrame(true, msg.rsv(), buf);
65              } else {
66                  buf.release();
67                  throw new IllegalStateException(
68                          "WebSocket frame was not of type TextWebSocketFrame or BinaryWebSocketFrame");
69              }
70              return;
71          }
72          if (msg instanceof ContinuationWebSocketFrame) {
73              if (tooLongFrameFound) {
74                  if (msg.isFinalFragment()) {
75                      currentFrame = null;
76                  }
77                  return;
78              }
79              CompositeByteBuf content = (CompositeByteBuf) currentFrame.content();
80              if (content.readableBytes() > maxFrameSize - msg.content().readableBytes()) {
81                  // release the current frame
82                  currentFrame.release();
83                  tooLongFrameFound = true;
84                  throw new TooLongFrameException(
85                          "WebSocketFrame length exceeded " + content +
86                                  " bytes.");
87              }
88              content.addComponent(msg.content().retain());
89              content.writerIndex(content.writerIndex() + msg.content().readableBytes());
90  
91              if (msg.isFinalFragment()) {
92                  WebSocketFrame currentFrame = this.currentFrame;
93                  this.currentFrame = null;
94                  out.add(currentFrame);
95                  return;
96              } else {
97                  return;
98              }
99          }
100         // It is possible to receive CLOSE/PING/PONG frames during fragmented frames so just pass them to the next
101         // handler in the chain
102         out.add(msg.retain());
103     }
104 
105     @Override
106     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
107         super.channelInactive(ctx);
108         // release current frame if it is not null as it may be a left-over
109         if (currentFrame != null) {
110             currentFrame.release();
111             currentFrame = null;
112         }
113     }
114 
115     @Override
116     public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
117         super.handlerRemoved(ctx);
118         // release current frame if it is not null as it may be a left-over as there is not much more we can do in
119         // this case
120         if (currentFrame != null) {
121             currentFrame.release();
122             currentFrame = null;
123         }
124     }
125 }