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.spdy;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.ChannelFuture;
20  import io.netty.channel.ChannelFutureListener;
21  import io.netty.channel.ChannelHandler;
22  import io.netty.channel.ChannelHandlerContext;
23  import io.netty.channel.ChannelPromise;
24  import io.netty.handler.codec.ByteToMessageDecoder;
25  import io.netty.handler.codec.UnsupportedMessageTypeException;
26  
27  import java.util.List;
28  
29  /**
30   * A {@link ChannelHandler} that encodes and decodes SPDY Frames.
31   */
32  public class SpdyFrameCodec extends ByteToMessageDecoder implements SpdyFrameDecoderDelegate {
33  
34      private static final SpdyProtocolException INVALID_FRAME =
35              new SpdyProtocolException("Received invalid frame");
36  
37      private final SpdyFrameDecoder spdyFrameDecoder;
38      private final SpdyFrameEncoder spdyFrameEncoder;
39      private final SpdyHeaderBlockDecoder spdyHeaderBlockDecoder;
40      private final SpdyHeaderBlockEncoder spdyHeaderBlockEncoder;
41  
42      private SpdyHeadersFrame spdyHeadersFrame;
43      private SpdySettingsFrame spdySettingsFrame;
44  
45      private ChannelHandlerContext ctx;
46  
47      /**
48       * Creates a new instance with the specified {@code version} and
49       * the default decoder and encoder options
50       * ({@code maxChunkSize (8192)}, {@code maxHeaderSize (16384)},
51       * {@code compressionLevel (6)}, {@code windowBits (15)},
52       * and {@code memLevel (8)}).
53       */
54      public SpdyFrameCodec(SpdyVersion version) {
55          this(version, 8192, 16384, 6, 15, 8);
56      }
57  
58      /**
59       * Creates a new instance with the specified decoder and encoder options.
60       */
61      public SpdyFrameCodec(
62              SpdyVersion version, int maxChunkSize, int maxHeaderSize,
63              int compressionLevel, int windowBits, int memLevel) {
64          this(version, maxChunkSize,
65                  SpdyHeaderBlockDecoder.newInstance(version, maxHeaderSize),
66                  SpdyHeaderBlockEncoder.newInstance(version, compressionLevel, windowBits, memLevel));
67      }
68  
69      protected SpdyFrameCodec(SpdyVersion version, int maxChunkSize,
70              SpdyHeaderBlockDecoder spdyHeaderBlockDecoder, SpdyHeaderBlockEncoder spdyHeaderBlockEncoder) {
71          spdyFrameDecoder = new SpdyFrameDecoder(version, this, maxChunkSize);
72          spdyFrameEncoder = new SpdyFrameEncoder(version);
73          this.spdyHeaderBlockDecoder = spdyHeaderBlockDecoder;
74          this.spdyHeaderBlockEncoder = spdyHeaderBlockEncoder;
75      }
76  
77      @Override
78      public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
79          super.handlerAdded(ctx);
80          this.ctx = ctx;
81          ctx.channel().closeFuture().addListener(new ChannelFutureListener() {
82              @Override
83              public void operationComplete(ChannelFuture future) throws Exception {
84                  spdyHeaderBlockDecoder.end();
85                  spdyHeaderBlockEncoder.end();
86              }
87          });
88      }
89  
90      @Override
91      protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
92          spdyFrameDecoder.decode(in);
93      }
94  
95      @Override
96      public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
97          ByteBuf frame;
98  
99          if (msg instanceof SpdyDataFrame) {
100 
101             SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
102             frame = spdyFrameEncoder.encodeDataFrame(
103                     ctx.alloc(),
104                     spdyDataFrame.streamId(),
105                     spdyDataFrame.isLast(),
106                     spdyDataFrame.content()
107             );
108             spdyDataFrame.release();
109             ctx.write(frame, promise);
110 
111         } else if (msg instanceof SpdySynStreamFrame) {
112 
113             SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
114             ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(ctx.alloc(), spdySynStreamFrame);
115             try {
116                 frame = spdyFrameEncoder.encodeSynStreamFrame(
117                         ctx.alloc(),
118                         spdySynStreamFrame.streamId(),
119                         spdySynStreamFrame.associatedStreamId(),
120                         spdySynStreamFrame.priority(),
121                         spdySynStreamFrame.isLast(),
122                         spdySynStreamFrame.isUnidirectional(),
123                         headerBlock
124                 );
125             } finally {
126                 headerBlock.release();
127             }
128             ctx.write(frame, promise);
129 
130         } else if (msg instanceof SpdySynReplyFrame) {
131 
132             SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
133             ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(ctx.alloc(), spdySynReplyFrame);
134             try {
135                 frame = spdyFrameEncoder.encodeSynReplyFrame(
136                         ctx.alloc(),
137                         spdySynReplyFrame.streamId(),
138                         spdySynReplyFrame.isLast(),
139                         headerBlock
140                 );
141             } finally {
142                 headerBlock.release();
143             }
144             ctx.write(frame, promise);
145 
146         } else if (msg instanceof SpdyRstStreamFrame) {
147 
148             SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
149             frame = spdyFrameEncoder.encodeRstStreamFrame(
150                     ctx.alloc(),
151                     spdyRstStreamFrame.streamId(),
152                     spdyRstStreamFrame.status().code()
153             );
154             ctx.write(frame, promise);
155 
156         } else if (msg instanceof SpdySettingsFrame) {
157 
158             SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
159             frame = spdyFrameEncoder.encodeSettingsFrame(
160                     ctx.alloc(),
161                     spdySettingsFrame
162             );
163             ctx.write(frame, promise);
164 
165         } else if (msg instanceof SpdyPingFrame) {
166 
167             SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
168             frame = spdyFrameEncoder.encodePingFrame(
169                     ctx.alloc(),
170                     spdyPingFrame.id()
171             );
172             ctx.write(frame, promise);
173 
174         } else if (msg instanceof SpdyGoAwayFrame) {
175 
176             SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
177             frame = spdyFrameEncoder.encodeGoAwayFrame(
178                     ctx.alloc(),
179                     spdyGoAwayFrame.lastGoodStreamId(),
180                     spdyGoAwayFrame.status().code()
181             );
182             ctx.write(frame, promise);
183 
184         } else if (msg instanceof SpdyHeadersFrame) {
185 
186             SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
187             ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(ctx.alloc(), spdyHeadersFrame);
188             try {
189                 frame = spdyFrameEncoder.encodeHeadersFrame(
190                         ctx.alloc(),
191                         spdyHeadersFrame.streamId(),
192                         spdyHeadersFrame.isLast(),
193                         headerBlock
194                 );
195             } finally {
196                 headerBlock.release();
197             }
198             ctx.write(frame, promise);
199 
200         } else if (msg instanceof SpdyWindowUpdateFrame) {
201 
202             SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
203             frame = spdyFrameEncoder.encodeWindowUpdateFrame(
204                     ctx.alloc(),
205                     spdyWindowUpdateFrame.streamId(),
206                     spdyWindowUpdateFrame.deltaWindowSize()
207             );
208             ctx.write(frame, promise);
209         } else {
210             throw new UnsupportedMessageTypeException(msg);
211         }
212     }
213 
214     @Override
215     public void readDataFrame(int streamId, boolean last, ByteBuf data) {
216         SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId, data);
217         spdyDataFrame.setLast(last);
218         ctx.fireChannelRead(spdyDataFrame);
219     }
220 
221     @Override
222     public void readSynStreamFrame(
223             int streamId, int associatedToStreamId, byte priority, boolean last, boolean unidirectional) {
224         SpdySynStreamFrame spdySynStreamFrame = new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority);
225         spdySynStreamFrame.setLast(last);
226         spdySynStreamFrame.setUnidirectional(unidirectional);
227         spdyHeadersFrame = spdySynStreamFrame;
228     }
229 
230     @Override
231     public void readSynReplyFrame(int streamId, boolean last) {
232         SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
233         spdySynReplyFrame.setLast(last);
234         spdyHeadersFrame = spdySynReplyFrame;
235     }
236 
237     @Override
238     public void readRstStreamFrame(int streamId, int statusCode) {
239         SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, statusCode);
240         ctx.fireChannelRead(spdyRstStreamFrame);
241     }
242 
243     @Override
244     public void readSettingsFrame(boolean clearPersisted) {
245         spdySettingsFrame = new DefaultSpdySettingsFrame();
246         spdySettingsFrame.setClearPreviouslyPersistedSettings(clearPersisted);
247     }
248 
249     @Override
250     public void readSetting(int id, int value, boolean persistValue, boolean persisted) {
251         spdySettingsFrame.setValue(id, value, persistValue, persisted);
252     }
253 
254     @Override
255     public void readSettingsEnd() {
256         Object frame = spdySettingsFrame;
257         spdySettingsFrame = null;
258         ctx.fireChannelRead(frame);
259     }
260 
261     @Override
262     public void readPingFrame(int id) {
263         SpdyPingFrame spdyPingFrame = new DefaultSpdyPingFrame(id);
264         ctx.fireChannelRead(spdyPingFrame);
265     }
266 
267     @Override
268     public void readGoAwayFrame(int lastGoodStreamId, int statusCode) {
269         SpdyGoAwayFrame spdyGoAwayFrame = new DefaultSpdyGoAwayFrame(lastGoodStreamId, statusCode);
270         ctx.fireChannelRead(spdyGoAwayFrame);
271     }
272 
273     @Override
274     public void readHeadersFrame(int streamId, boolean last) {
275         spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId);
276         spdyHeadersFrame.setLast(last);
277     }
278 
279     @Override
280     public void readWindowUpdateFrame(int streamId, int deltaWindowSize) {
281         SpdyWindowUpdateFrame spdyWindowUpdateFrame = new DefaultSpdyWindowUpdateFrame(streamId, deltaWindowSize);
282         ctx.fireChannelRead(spdyWindowUpdateFrame);
283     }
284 
285     @Override
286     public void readHeaderBlock(ByteBuf headerBlock) {
287         try {
288             spdyHeaderBlockDecoder.decode(ctx.alloc(), headerBlock, spdyHeadersFrame);
289         } catch (Exception e) {
290             ctx.fireExceptionCaught(e);
291         } finally {
292             headerBlock.release();
293         }
294     }
295 
296     @Override
297     public void readHeaderBlockEnd() {
298         Object frame = null;
299         try {
300             spdyHeaderBlockDecoder.endHeaderBlock(spdyHeadersFrame);
301             frame = spdyHeadersFrame;
302             spdyHeadersFrame = null;
303         } catch (Exception e) {
304             ctx.fireExceptionCaught(e);
305         }
306         if (frame != null) {
307             ctx.fireChannelRead(frame);
308         }
309     }
310 
311     @Override
312     public void readFrameError(String message) {
313         ctx.fireExceptionCaught(INVALID_FRAME);
314     }
315 }