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