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