View Javadoc

1   /*
2    * Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
17  
18  import static org.jboss.netty.handler.codec.spdy.SpdyCodecUtil.*;
19  
20  import java.util.HashMap;
21  import java.util.Map;
22  
23  import org.jboss.netty.buffer.ChannelBuffer;
24  import org.jboss.netty.buffer.ChannelBuffers;
25  import org.jboss.netty.channel.Channel;
26  import org.jboss.netty.channel.ChannelHandlerContext;
27  import org.jboss.netty.channel.Channels;
28  import org.jboss.netty.handler.codec.frame.TooLongFrameException;
29  import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
30  import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
31  import org.jboss.netty.handler.codec.http.HttpHeaders;
32  import org.jboss.netty.handler.codec.http.HttpMessage;
33  import org.jboss.netty.handler.codec.http.HttpMethod;
34  import org.jboss.netty.handler.codec.http.HttpRequest;
35  import org.jboss.netty.handler.codec.http.HttpResponse;
36  import org.jboss.netty.handler.codec.http.HttpResponseStatus;
37  import org.jboss.netty.handler.codec.http.HttpVersion;
38  import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
39  
40  /**
41   * Decodes {@link SpdySynStreamFrame}s, {@link SpdySynReplyFrame}s,
42   * and {@link SpdyDataFrame}s into {@link HttpRequest}s and {@link HttpResponse}s.
43   */
44  public class SpdyHttpDecoder extends OneToOneDecoder {
45  
46      private final int spdyVersion;
47      private final int maxContentLength;
48      private final Map<Integer, HttpMessage> messageMap;
49  
50      /**
51       * Creates a new instance for the SPDY/2 protocol
52       *
53       * @param maxContentLength the maximum length of the message content.
54       *        If the length of the message content exceeds this value,
55       *        a {@link TooLongFrameException} will be raised.
56       */
57      @Deprecated
58      public SpdyHttpDecoder(int maxContentLength) {
59          this(2, maxContentLength);
60      }
61  
62      /**
63       * Creates a new instance.
64       *
65       * @param version the protocol version
66       * @param maxContentLength the maximum length of the message content.
67       *        If the length of the message content exceeds this value,
68       *        a {@link TooLongFrameException} will be raised.
69       */
70      public SpdyHttpDecoder(int version, int maxContentLength) {
71          this(version, maxContentLength, new HashMap<Integer, HttpMessage>());
72      }
73  
74      /**
75       * Creates a new instance with the specified parameters.
76       *
77       * @param version the protocol version
78       * @param maxContentLength the maximum length of the message content.
79       *        If the length of the message content exceeds this value,
80       *        a {@link TooLongFrameException} will be raised.
81       * @param messageMap the {@link Map} used to hold partially received messages.
82       */
83      protected SpdyHttpDecoder(int version, int maxContentLength, Map<Integer, HttpMessage> messageMap) {
84          if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
85              throw new IllegalArgumentException(
86                      "unsupported version: " + version);
87          }
88          if (maxContentLength <= 0) {
89              throw new IllegalArgumentException(
90                      "maxContentLength must be a positive integer: " + maxContentLength);
91          }
92          spdyVersion = version;
93          this.maxContentLength = maxContentLength;
94          this.messageMap = messageMap;
95      }
96  
97      protected HttpMessage putMessage(int streamId, HttpMessage message) {
98          return messageMap.put(streamId, message);
99      }
100 
101     protected HttpMessage getMessage(int streamId) {
102         return messageMap.get(streamId);
103     }
104 
105     protected HttpMessage removeMessage(int streamId) {
106         return messageMap.remove(streamId);
107     }
108 
109     @Override
110     protected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg)
111             throws Exception {
112 
113         if (msg instanceof SpdySynStreamFrame) {
114 
115             // HTTP requests/responses are mapped one-to-one to SPDY streams.
116             SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
117             int streamId = spdySynStreamFrame.getStreamId();
118 
119             if (isServerId(streamId)) {
120                 // SYN_STREAM frames initiated by the server are pushed resources
121                 int associatedToStreamId = spdySynStreamFrame.getAssociatedToStreamId();
122 
123                 // If a client receives a SYN_STREAM with an Associated-To-Stream-ID of 0
124                 // it must reply with a RST_STREAM with error code INVALID_STREAM
125                 if (associatedToStreamId == 0) {
126                     SpdyRstStreamFrame spdyRstStreamFrame =
127                         new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INVALID_STREAM);
128                     Channels.write(ctx, Channels.future(channel), spdyRstStreamFrame);
129                 }
130 
131                 String URL = SpdyHeaders.getUrl(spdyVersion, spdySynStreamFrame);
132 
133                 // If a client receives a SYN_STREAM without a 'url' header
134                 // it must reply with a RST_STREAM with error code PROTOCOL_ERROR
135                 if (URL == null) {
136                     SpdyRstStreamFrame spdyRstStreamFrame =
137                         new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
138                     Channels.write(ctx, Channels.future(channel), spdyRstStreamFrame);
139                 }
140 
141                 try {
142                     HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynStreamFrame);
143 
144                     // Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers
145                     SpdyHttpHeaders.setStreamId(httpResponse, streamId);
146                     SpdyHttpHeaders.setAssociatedToStreamId(httpResponse, associatedToStreamId);
147                     SpdyHttpHeaders.setPriority(httpResponse, spdySynStreamFrame.getPriority());
148                     SpdyHttpHeaders.setUrl(httpResponse, URL);
149 
150                     if (spdySynStreamFrame.isLast()) {
151                         HttpHeaders.setContentLength(httpResponse, 0);
152                         return httpResponse;
153                     } else {
154                         // Response body will follow in a series of Data Frames
155                         putMessage(streamId, httpResponse);
156                     }
157                 } catch (Exception e) {
158                     SpdyRstStreamFrame spdyRstStreamFrame =
159                         new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
160                     Channels.write(ctx, Channels.future(channel), spdyRstStreamFrame);
161                 }
162             } else {
163                 // SYN_STREAM frames initiated by the client are HTTP requests
164                 try {
165                     HttpRequest httpRequest = createHttpRequest(spdyVersion, spdySynStreamFrame);
166 
167                     // Set the Stream-ID as a header
168                     SpdyHttpHeaders.setStreamId(httpRequest, streamId);
169 
170                     if (spdySynStreamFrame.isLast()) {
171                         return httpRequest;
172                     } else {
173                         // Request body will follow in a series of Data Frames
174                         putMessage(streamId, httpRequest);
175                     }
176                 } catch (Exception e) {
177                     // If a client sends a SYN_STREAM without all of the method, url (host and path),
178                     // scheme, and version headers the server must reply with a HTTP 400 BAD REQUEST reply.
179                     // Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid
180                     SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
181                     spdySynReplyFrame.setLast(true);
182                     SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST);
183                     SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0);
184                     Channels.write(ctx, Channels.future(channel), spdySynReplyFrame);
185                 }
186             }
187 
188         } else if (msg instanceof SpdySynReplyFrame) {
189 
190             SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
191             int streamId = spdySynReplyFrame.getStreamId();
192 
193             try {
194                 HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynReplyFrame);
195 
196                 // Set the Stream-ID as a header
197                 SpdyHttpHeaders.setStreamId(httpResponse, streamId);
198 
199                 if (spdySynReplyFrame.isLast()) {
200                     HttpHeaders.setContentLength(httpResponse, 0);
201                     return httpResponse;
202                 } else {
203                     // Response body will follow in a series of Data Frames
204                     putMessage(streamId, httpResponse);
205                 }
206             } catch (Exception e) {
207                 // If a client receives a SYN_REPLY without valid status and version headers
208                 // the client must reply with a RST_STREAM frame indicating a PROTOCOL_ERROR
209                 SpdyRstStreamFrame spdyRstStreamFrame =
210                     new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
211                 Channels.write(ctx, Channels.future(channel), spdyRstStreamFrame);
212             }
213 
214         } else if (msg instanceof SpdyHeadersFrame) {
215 
216             SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
217             int streamId = spdyHeadersFrame.getStreamId();
218             HttpMessage httpMessage = getMessage(streamId);
219 
220             // If message is not in map discard HEADERS frame.
221             if (httpMessage == null) {
222                 return null;
223             }
224 
225             for (Map.Entry<String, String> e: spdyHeadersFrame.getHeaders()) {
226                 httpMessage.addHeader(e.getKey(), e.getValue());
227             }
228 
229             if (spdyHeadersFrame.isLast()) {
230                 HttpHeaders.setContentLength(httpMessage, httpMessage.getContent().readableBytes());
231                 removeMessage(streamId);
232                 return httpMessage;
233             }
234 
235         } else if (msg instanceof SpdyDataFrame) {
236 
237             SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
238             int streamId = spdyDataFrame.getStreamId();
239             HttpMessage httpMessage = getMessage(streamId);
240 
241             // If message is not in map discard Data Frame.
242             if (httpMessage == null) {
243                 return null;
244             }
245 
246             ChannelBuffer content = httpMessage.getContent();
247             if (content.readableBytes() > maxContentLength - spdyDataFrame.getData().readableBytes()) {
248                 removeMessage(streamId);
249                 throw new TooLongFrameException(
250                         "HTTP content length exceeded " + maxContentLength + " bytes.");
251             }
252 
253             if (content == ChannelBuffers.EMPTY_BUFFER) {
254                 content = ChannelBuffers.dynamicBuffer(channel.getConfig().getBufferFactory());
255                 content.writeBytes(spdyDataFrame.getData());
256                 httpMessage.setContent(content);
257             } else {
258                 content.writeBytes(spdyDataFrame.getData());
259             }
260 
261             if (spdyDataFrame.isLast()) {
262                 HttpHeaders.setContentLength(httpMessage, content.readableBytes());
263                 removeMessage(streamId);
264                 return httpMessage;
265             }
266 
267         } else if (msg instanceof SpdyRstStreamFrame) {
268 
269             SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
270             int streamId = spdyRstStreamFrame.getStreamId();
271             removeMessage(streamId);
272         }
273 
274         return null;
275     }
276 
277     private static HttpRequest createHttpRequest(int spdyVersion, SpdyHeaderBlock requestFrame)
278             throws Exception {
279         // Create the first line of the request from the name/value pairs
280         HttpMethod  method      = SpdyHeaders.getMethod(spdyVersion, requestFrame);
281         String      url         = SpdyHeaders.getUrl(spdyVersion, requestFrame);
282         HttpVersion httpVersion = SpdyHeaders.getVersion(spdyVersion, requestFrame);
283         SpdyHeaders.removeMethod(spdyVersion, requestFrame);
284         SpdyHeaders.removeUrl(spdyVersion, requestFrame);
285         SpdyHeaders.removeVersion(spdyVersion, requestFrame);
286 
287         HttpRequest httpRequest = new DefaultHttpRequest(httpVersion, method, url);
288 
289         // Remove the scheme header
290         SpdyHeaders.removeScheme(spdyVersion, requestFrame);
291 
292         if (spdyVersion >= 3) {
293             // Replace the SPDY host header with the HTTP host header
294             String host = SpdyHeaders.getHost(requestFrame);
295             SpdyHeaders.removeHost(requestFrame);
296             HttpHeaders.setHost(httpRequest, host);
297         }
298 
299         for (Map.Entry<String, String> e: requestFrame.getHeaders()) {
300             httpRequest.addHeader(e.getKey(), e.getValue());
301         }
302 
303         // The Connection and Keep-Alive headers are no longer valid
304         HttpHeaders.setKeepAlive(httpRequest, true);
305 
306         // Transfer-Encoding header is not valid
307         httpRequest.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
308 
309         return httpRequest;
310     }
311 
312     private static HttpResponse createHttpResponse(int spdyVersion, SpdyHeaderBlock responseFrame)
313             throws Exception {
314         // Create the first line of the response from the name/value pairs
315         HttpResponseStatus status = SpdyHeaders.getStatus(spdyVersion, responseFrame);
316         HttpVersion version = SpdyHeaders.getVersion(spdyVersion, responseFrame);
317         SpdyHeaders.removeStatus(spdyVersion, responseFrame);
318         SpdyHeaders.removeVersion(spdyVersion, responseFrame);
319 
320         HttpResponse httpResponse = new DefaultHttpResponse(version, status);
321         for (Map.Entry<String, String> e: responseFrame.getHeaders()) {
322             httpResponse.addHeader(e.getKey(), e.getValue());
323         }
324 
325         // The Connection and Keep-Alive headers are no longer valid
326         HttpHeaders.setKeepAlive(httpResponse, true);
327 
328         // Transfer-Encoding header is not valid
329         httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
330         httpResponse.removeHeader(HttpHeaders.Names.TRAILER);
331 
332         return httpResponse;
333     }
334 }