From 017a5ef4e4cf07a410435573677def5b084494e3 Mon Sep 17 00:00:00 2001 From: Jeff Pinner Date: Sat, 10 Jan 2015 10:53:54 -0800 Subject: [PATCH] SPDY: fix support for pushed resources in SpdyHttpEncoder Motivation: The SpdyHttpDecoder was modified to support pushed resources that are divided into multiple frames. The decoder accepts a pushed SpdySynStreamFrame containing the request headers, followed by a SpdyHeadersFrame containing the response headers. Modifications: This commit modifies the SpdyHttpEncoder so that it encodes pushed resources in a format that the SpdyHttpDecoder can decode. The encoder will accept an HttpRequest object containing the request headers, followed by an HttpResponse object containing the response headers. Result: The SpdyHttpEncoder will create a SpdySynStreamFrame followed by a SpdyHeadersFrame when sending pushed resources. --- .../handler/codec/spdy/SpdyHttpEncoder.java | 142 ++++++++---------- .../handler/codec/spdy/SpdyHttpHeaders.java | 25 --- 2 files changed, 64 insertions(+), 103 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java index d392ce56fb..9b9617a731 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java @@ -19,7 +19,6 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.handler.codec.UnsupportedMessageTypeException; import io.netty.handler.codec.http.FullHttpMessage; -import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMessage; @@ -71,7 +70,7 @@ import java.util.Map; * *

Pushed Resource Annotations

* - * SPDY specific headers must be added to pushed {@link HttpResponse}s: + * SPDY specific headers must be added to pushed {@link HttpRequest}s: * * * @@ -92,10 +91,6 @@ import java.util.Map; * 0 represents the highest priority and 7 represents the lowest. * This header is optional and defaults to 0. * - * - * - * - * *
Header NameHeader Value
{@code "X-SPDY-URL"}The absolute path for the resource being pushed.
* *

Required Annotations

@@ -122,7 +117,6 @@ import java.util.Map; */ public class SpdyHttpEncoder extends MessageToMessageEncoder { - private final int spdyVersion; private int currentStreamId; /** @@ -134,7 +128,6 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { if (version == null) { throw new NullPointerException("version"); } - spdyVersion = version.getVersion(); } @Override @@ -149,22 +142,16 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpRequest); out.add(spdySynStreamFrame); - last = spdySynStreamFrame.isLast(); + last = spdySynStreamFrame.isLast() || spdySynStreamFrame.isUnidirectional(); valid = true; } if (msg instanceof HttpResponse) { HttpResponse httpResponse = (HttpResponse) msg; - if (httpResponse.headers().contains(SpdyHttpHeaders.Names.ASSOCIATED_TO_STREAM_ID)) { - SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpResponse); - last = spdySynStreamFrame.isLast(); - out.add(spdySynStreamFrame); - } else { - SpdySynReplyFrame spdySynReplyFrame = createSynReplyFrame(httpResponse); - last = spdySynReplyFrame.isLast(); - out.add(spdySynReplyFrame); - } + SpdyHeadersFrame spdyHeadersFrame = createHeadersFrame(httpResponse); + out.add(spdyHeadersFrame); + last = spdyHeadersFrame.isLast(); valid = true; } if (msg instanceof HttpContent && !last) { @@ -173,22 +160,23 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { chunk.content().retain(); SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(currentStreamId, chunk.content()); - spdyDataFrame.setLast(chunk instanceof LastHttpContent); if (chunk instanceof LastHttpContent) { LastHttpContent trailer = (LastHttpContent) chunk; HttpHeaders trailers = trailer.trailingHeaders(); if (trailers.isEmpty()) { + spdyDataFrame.setLast(true); out.add(spdyDataFrame); } else { // Create SPDY HEADERS frame out of trailers SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(currentStreamId); + spdyHeadersFrame.setLast(true); for (Map.Entry entry: trailers) { spdyHeadersFrame.headers().add(entry.getKey(), entry.getValue()); } - // Write HEADERS frame and append Data Frame - out.add(spdyHeadersFrame); + // Write DATA frame and append HEADERS frame out.add(spdyDataFrame); + out.add(spdyHeadersFrame); } } else { out.add(spdyDataFrame); @@ -202,96 +190,94 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { } } - private SpdySynStreamFrame createSynStreamFrame(HttpMessage httpMessage) - throws Exception { - // Get the Stream-ID, Associated-To-Stream-ID, Priority, URL, and scheme from the headers - int streamID = SpdyHttpHeaders.getStreamId(httpMessage); - int associatedToStreamId = SpdyHttpHeaders.getAssociatedToStreamId(httpMessage); - byte priority = SpdyHttpHeaders.getPriority(httpMessage); - String URL = SpdyHttpHeaders.getUrl(httpMessage); - String scheme = SpdyHttpHeaders.getScheme(httpMessage); - SpdyHttpHeaders.removeStreamId(httpMessage); - SpdyHttpHeaders.removeAssociatedToStreamId(httpMessage); - SpdyHttpHeaders.removePriority(httpMessage); - SpdyHttpHeaders.removeUrl(httpMessage); - SpdyHttpHeaders.removeScheme(httpMessage); + @SuppressWarnings("deprecation") + private SpdySynStreamFrame createSynStreamFrame(HttpRequest httpRequest) throws Exception { + // Get the Stream-ID, Associated-To-Stream-ID, Priority, and scheme from the headers + final HttpHeaders httpHeaders = httpRequest.headers(); + int streamId = SpdyHttpHeaders.getStreamId(httpRequest); + int associatedToStreamId = SpdyHttpHeaders.getAssociatedToStreamId(httpRequest); + byte priority = SpdyHttpHeaders.getPriority(httpRequest); + CharSequence scheme = httpHeaders.get(SpdyHttpHeaders.Names.SCHEME); + httpHeaders.remove(SpdyHttpHeaders.Names.STREAM_ID); + httpHeaders.remove(SpdyHttpHeaders.Names.ASSOCIATED_TO_STREAM_ID); + httpHeaders.remove(SpdyHttpHeaders.Names.PRIORITY); + httpHeaders.remove(SpdyHttpHeaders.Names.SCHEME); // The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding // headers are not valid and MUST not be sent. - httpMessage.headers().remove(HttpHeaders.Names.CONNECTION); - httpMessage.headers().remove("Keep-Alive"); - httpMessage.headers().remove("Proxy-Connection"); - httpMessage.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING); + httpHeaders.remove(HttpHeaders.Names.CONNECTION); + httpHeaders.remove("Keep-Alive"); + httpHeaders.remove("Proxy-Connection"); + httpHeaders.remove(HttpHeaders.Names.TRANSFER_ENCODING); SpdySynStreamFrame spdySynStreamFrame = - new DefaultSpdySynStreamFrame(streamID, associatedToStreamId, priority); + new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority); // Unfold the first line of the message into name/value pairs - if (httpMessage instanceof FullHttpRequest) { - HttpRequest httpRequest = (HttpRequest) httpMessage; - SpdyHeaders.setMethod(spdyVersion, spdySynStreamFrame, httpRequest.getMethod()); - SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, httpRequest.getUri()); - SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion()); - } - if (httpMessage instanceof HttpResponse) { - HttpResponse httpResponse = (HttpResponse) httpMessage; - SpdyHeaders.setStatus(spdyVersion, spdySynStreamFrame, httpResponse.getStatus()); - SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, URL); - SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion()); - spdySynStreamFrame.setUnidirectional(true); - } + SpdyHeaders frameHeaders = spdySynStreamFrame.headers(); + frameHeaders.set(SpdyHeaders.HttpNames.METHOD, httpRequest.getMethod()); + frameHeaders.set(SpdyHeaders.HttpNames.PATH, httpRequest.getUri()); + frameHeaders.set(SpdyHeaders.HttpNames.VERSION, httpRequest.getProtocolVersion()); // Replace the HTTP host header with the SPDY host header - if (spdyVersion >= 3) { - String host = HttpHeaders.getHost(httpMessage); - httpMessage.headers().remove(HttpHeaders.Names.HOST); - SpdyHeaders.setHost(spdySynStreamFrame, host); - } + CharSequence host = httpHeaders.get(HttpHeaders.Names.HOST); + httpHeaders.remove(HttpHeaders.Names.HOST); + frameHeaders.set(SpdyHeaders.HttpNames.HOST, host); // Set the SPDY scheme header if (scheme == null) { scheme = "https"; } - SpdyHeaders.setScheme(spdyVersion, spdySynStreamFrame, scheme); + frameHeaders.set(SpdyHeaders.HttpNames.SCHEME, scheme); // Transfer the remaining HTTP headers - for (Map.Entry entry: httpMessage.headers()) { - spdySynStreamFrame.headers().add(entry.getKey(), entry.getValue()); + for (Map.Entry entry: httpHeaders) { + frameHeaders.add(entry.getKey(), entry.getValue()); } currentStreamId = spdySynStreamFrame.streamId(); - spdySynStreamFrame.setLast(isLast(httpMessage)); + if (associatedToStreamId == 0) { + spdySynStreamFrame.setLast(isLast(httpRequest)); + } else { + spdySynStreamFrame.setUnidirectional(true); + } return spdySynStreamFrame; } - private SpdySynReplyFrame createSynReplyFrame(HttpResponse httpResponse) - throws Exception { + @SuppressWarnings("deprecation") + private SpdyHeadersFrame createHeadersFrame(HttpResponse httpResponse) throws Exception { // Get the Stream-ID from the headers - int streamID = SpdyHttpHeaders.getStreamId(httpResponse); - SpdyHttpHeaders.removeStreamId(httpResponse); + final HttpHeaders httpHeaders = httpResponse.headers(); + int streamId = SpdyHttpHeaders.getStreamId(httpResponse); + httpHeaders.remove(SpdyHttpHeaders.Names.STREAM_ID); // The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding // headers are not valid and MUST not be sent. - httpResponse.headers().remove(HttpHeaders.Names.CONNECTION); - httpResponse.headers().remove("Keep-Alive"); - httpResponse.headers().remove("Proxy-Connection"); - httpResponse.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING); - - SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID); + httpHeaders.remove(HttpHeaders.Names.CONNECTION); + httpHeaders.remove("Keep-Alive"); + httpHeaders.remove("Proxy-Connection"); + httpHeaders.remove(HttpHeaders.Names.TRANSFER_ENCODING); + SpdyHeadersFrame spdyHeadersFrame; + if (SpdyCodecUtil.isServerId(streamId)) { + spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId); + } else { + spdyHeadersFrame = new DefaultSpdySynReplyFrame(streamId); + } + SpdyHeaders frameHeaders = spdyHeadersFrame.headers(); // Unfold the first line of the response into name/value pairs - SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, httpResponse.getStatus()); - SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, httpResponse.getProtocolVersion()); + frameHeaders.set(SpdyHeaders.HttpNames.STATUS, httpResponse.getStatus().code()); + frameHeaders.set(SpdyHeaders.HttpNames.VERSION, httpResponse.getProtocolVersion()); // Transfer the remaining HTTP headers - for (Map.Entry entry: httpResponse.headers()) { - spdySynReplyFrame.headers().add(entry.getKey(), entry.getValue()); + for (Map.Entry entry: httpHeaders) { + spdyHeadersFrame.headers().add(entry.getKey(), entry.getValue()); } - currentStreamId = streamID; - spdySynReplyFrame.setLast(isLast(httpResponse)); + currentStreamId = streamId; + spdyHeadersFrame.setLast(isLast(httpResponse)); - return spdySynReplyFrame; + return spdyHeadersFrame; } /** diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpHeaders.java index 0aa0de913d..771f0dfe70 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpHeaders.java @@ -40,10 +40,6 @@ public final class SpdyHttpHeaders { * {@code "X-SPDY-Priority"} */ public static final String PRIORITY = "X-SPDY-Priority"; - /** - * {@code "X-SPDY-URL"} - */ - public static final String URL = "X-SPDY-URL"; /** * {@code "X-SPDY-Scheme"} */ @@ -124,27 +120,6 @@ public final class SpdyHttpHeaders { HttpHeaders.setIntHeader(message, Names.PRIORITY, priority); } - /** - * Removes the {@code "X-SPDY-URL"} header. - */ - public static void removeUrl(HttpMessage message) { - message.headers().remove(Names.URL); - } - - /** - * Returns the value of the {@code "X-SPDY-URL"} header. - */ - public static String getUrl(HttpMessage message) { - return message.headers().get(Names.URL); - } - - /** - * Sets the {@code "X-SPDY-URL"} header. - */ - public static void setUrl(HttpMessage message, String url) { - message.headers().set(Names.URL, url); - } - /** * Removes the {@code "X-SPDY-Scheme"} header. */