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