From 6f80fdcac49436fa404c57a5715ca75ae50dbb4d Mon Sep 17 00:00:00 2001 From: Jeff Pinner Date: Fri, 14 Nov 2014 15:07:00 -0800 Subject: [PATCH] SPDY: add support for pushed resources in SpdyHttpDecoder Motivation: The SPDY/3.1 spec does not adequate describe how to push resources from the server. This was solidified in the HTTP/2 drafts by dividing the push into two frames, a PushPromise containing the request, followed by a Headers frame containing the response. Modifications: This commit modifies the SpdyHttpDecoder to support pushed resources that are divided into multiple frames. The decoder will accept a pushed SpdySynStreamFrame containing the request headers, followed by a SpdyHeadersFrame containing the response headers. Result: The SpdyHttpDecoder will create an HttpRequest object followed by an HttpResponse object when receiving pushed resources. --- .../handler/codec/spdy/SpdyHttpDecoder.java | 67 +++++++++++++------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java index 4c87f98651..de02449849 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java @@ -137,7 +137,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { int associatedToStreamId = spdySynStreamFrame.associatedStreamId(); // If a client receives a SYN_STREAM with an Associated-To-Stream-ID of 0 - // it must reply with a RST_STREAM with error code INVALID_STREAM + // it must reply with a RST_STREAM with error code INVALID_STREAM. if (associatedToStreamId == 0) { SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INVALID_STREAM); @@ -145,12 +145,10 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { return; } - CharSequence URL = spdySynStreamFrame.headers().get(PATH); - spdySynStreamFrame.headers().remove(PATH); - - // If a client receives a SYN_STREAM without a 'url' header - // it must reply with a RST_STREAM with error code PROTOCOL_ERROR - if (URL == null) { + // If a client receives a SYN_STREAM with isLast set, + // reply with a RST_STREAM with error code PROTOCOL_ERROR + // (we only support pushed resources divided into two header blocks). + if (spdySynStreamFrame.isLast()) { SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR); ctx.writeAndFlush(spdyRstStreamFrame); @@ -167,22 +165,15 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { } try { - FullHttpResponse httpResponseWithEntity = - createHttpResponse(ctx, spdySynStreamFrame, validateHeaders); + FullHttpRequest httpRequestWithEntity = createHttpRequest(spdyVersion, spdySynStreamFrame); - // Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers - httpResponseWithEntity.headers().setInt(Names.STREAM_ID, streamId); - httpResponseWithEntity.headers().setInt(Names.ASSOCIATED_TO_STREAM_ID, associatedToStreamId); - httpResponseWithEntity.headers().setInt(Names.PRIORITY, spdySynStreamFrame.priority()); - httpResponseWithEntity.headers().set(Names.URL, URL); + // Set the Stream-ID, Associated-To-Stream-ID, iand Priority as headers + httpRequestWithEntity.headers().setInt(Names.STREAM_ID, streamId); + httpRequestWithEntity.headers().setInt(Names.ASSOCIATED_TO_STREAM_ID, associatedToStreamId); + httpRequestWithEntity.headers().setInt(Names.PRIORITY, spdySynStreamFrame.priority()); + + out.add(httpRequestWithEntity); - if (spdySynStreamFrame.isLast()) { - HttpHeaderUtil.setContentLength(httpResponseWithEntity, 0); - out.add(httpResponseWithEntity); - } else { - // Response body will follow in a series of Data Frames - putMessage(streamId, httpResponseWithEntity); - } } catch (Exception ignored) { SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR); @@ -269,8 +260,40 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { int streamId = spdyHeadersFrame.streamId(); FullHttpMessage fullHttpMessage = getMessage(streamId); - // If message is not in map discard HEADERS frame. if (fullHttpMessage == null) { + // HEADERS frames may initiate a pushed response + if (SpdyCodecUtil.isServerId(streamId)) { + + // If a client receives a HEADERS with a truncated header block, + // reply with a RST_STREAM frame with error code INTERNAL_ERROR. + if (spdyHeadersFrame.isTruncated()) { + SpdyRstStreamFrame spdyRstStreamFrame = + new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INTERNAL_ERROR); + ctx.writeAndFlush(spdyRstStreamFrame); + return; + } + + try { + fullHttpMessage = createHttpResponse(ctx, spdyHeadersFrame, validateHeaders); + + // Set the Stream-ID as a header + fullHttpMessage.headers().setInt(Names.STREAM_ID, streamId); + + if (spdyHeadersFrame.isLast()) { + HttpHeaderUtil.setContentLength(fullHttpMessage, 0); + out.add(fullHttpMessage); + } else { + // Response body will follow in a series of Data Frames + putMessage(streamId, fullHttpMessage); + } + } catch (Exception e) { + // If a client receives a SYN_REPLY without valid getStatus and version headers + // the client must reply with a RST_STREAM frame indicating a PROTOCOL_ERROR + SpdyRstStreamFrame spdyRstStreamFrame = + new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR); + ctx.writeAndFlush(spdyRstStreamFrame); + } + } return; }