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.
This commit is contained in:
Jeff Pinner 2015-01-10 10:53:54 -08:00 committed by Trustin Lee
parent c4630c0328
commit 04dd885421
2 changed files with 43 additions and 64 deletions

View File

@ -19,7 +19,6 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.UnsupportedMessageTypeException; import io.netty.handler.codec.UnsupportedMessageTypeException;
import io.netty.handler.codec.http.FullHttpMessage; 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.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpHeaders;
@ -75,7 +74,7 @@ import static io.netty.handler.codec.spdy.SpdyHeaders.HttpNames.*;
* *
* <h3>Pushed Resource Annotations</h3> * <h3>Pushed Resource Annotations</h3>
* *
* SPDY specific headers must be added to pushed {@link HttpResponse}s: * SPDY specific headers must be added to pushed {@link HttpRequest}s:
* <table border=1> * <table border=1>
* <tr> * <tr>
* <th>Header Name</th><th>Header Value</th> * <th>Header Name</th><th>Header Value</th>
@ -96,10 +95,6 @@ import static io.netty.handler.codec.spdy.SpdyHeaders.HttpNames.*;
* 0 represents the highest priority and 7 represents the lowest. * 0 represents the highest priority and 7 represents the lowest.
* This header is optional and defaults to 0.</td> * This header is optional and defaults to 0.</td>
* </tr> * </tr>
* <tr>
* <td>{@code "X-SPDY-URL"}</td>
* <td>The absolute path for the resource being pushed.</td>
* </tr>
* </table> * </table>
* *
* <h3>Required Annotations</h3> * <h3>Required Annotations</h3>
@ -126,7 +121,6 @@ import static io.netty.handler.codec.spdy.SpdyHeaders.HttpNames.*;
*/ */
public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> { public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
private final int spdyVersion;
private int currentStreamId; private int currentStreamId;
/** /**
@ -138,7 +132,6 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
if (version == null) { if (version == null) {
throw new NullPointerException("version"); throw new NullPointerException("version");
} }
spdyVersion = version.getVersion();
} }
@Override @Override
@ -153,22 +146,16 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpRequest); SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpRequest);
out.add(spdySynStreamFrame); out.add(spdySynStreamFrame);
last = spdySynStreamFrame.isLast(); last = spdySynStreamFrame.isLast() || spdySynStreamFrame.isUnidirectional();
valid = true; valid = true;
} }
if (msg instanceof HttpResponse) { if (msg instanceof HttpResponse) {
HttpResponse httpResponse = (HttpResponse) msg; HttpResponse httpResponse = (HttpResponse) msg;
if (httpResponse.headers().contains(SpdyHttpHeaders.Names.ASSOCIATED_TO_STREAM_ID)) { SpdyHeadersFrame spdyHeadersFrame = createHeadersFrame(httpResponse);
SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpResponse); out.add(spdyHeadersFrame);
last = spdySynStreamFrame.isLast();
out.add(spdySynStreamFrame);
} else {
SpdySynReplyFrame spdySynReplyFrame = createSynReplyFrame(httpResponse);
last = spdySynReplyFrame.isLast();
out.add(spdySynReplyFrame);
}
last = spdyHeadersFrame.isLast();
valid = true; valid = true;
} }
if (msg instanceof HttpContent && !last) { if (msg instanceof HttpContent && !last) {
@ -177,22 +164,23 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
chunk.content().retain(); chunk.content().retain();
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(currentStreamId, chunk.content()); SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(currentStreamId, chunk.content());
spdyDataFrame.setLast(chunk instanceof LastHttpContent);
if (chunk instanceof LastHttpContent) { if (chunk instanceof LastHttpContent) {
LastHttpContent trailer = (LastHttpContent) chunk; LastHttpContent trailer = (LastHttpContent) chunk;
HttpHeaders trailers = trailer.trailingHeaders(); HttpHeaders trailers = trailer.trailingHeaders();
if (trailers.isEmpty()) { if (trailers.isEmpty()) {
spdyDataFrame.setLast(true);
out.add(spdyDataFrame); out.add(spdyDataFrame);
} else { } else {
// Create SPDY HEADERS frame out of trailers // Create SPDY HEADERS frame out of trailers
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(currentStreamId); SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(currentStreamId);
spdyHeadersFrame.setLast(true);
for (Map.Entry<String, String> entry: trailers) { for (Map.Entry<String, String> entry: trailers) {
spdyHeadersFrame.headers().add(entry.getKey(), entry.getValue()); spdyHeadersFrame.headers().add(entry.getKey(), entry.getValue());
} }
// Write HEADERS frame and append Data Frame // Write DATA frame and append HEADERS frame
out.add(spdyHeadersFrame);
out.add(spdyDataFrame); out.add(spdyDataFrame);
out.add(spdyHeadersFrame);
} }
} else { } else {
out.add(spdyDataFrame); out.add(spdyDataFrame);
@ -206,19 +194,17 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
} }
} }
private SpdySynStreamFrame createSynStreamFrame(HttpMessage httpMessage) @SuppressWarnings("deprecation")
throws Exception { private SpdySynStreamFrame createSynStreamFrame(HttpRequest httpRequest) throws Exception {
// Get the Stream-ID, Associated-To-Stream-ID, Priority, URL, and scheme from the headers // Get the Stream-ID, Associated-To-Stream-ID, Priority, and scheme from the headers
final HttpHeaders httpHeaders = httpMessage.headers(); final HttpHeaders httpHeaders = httpRequest.headers();
int streamID = httpMessage.headers().getInt(Names.STREAM_ID); int streamId = httpHeaders.getInt(Names.STREAM_ID);
int associatedToStreamId = httpMessage.headers().getInt(Names.ASSOCIATED_TO_STREAM_ID, 0); int associatedToStreamId = httpHeaders.getInt(Names.ASSOCIATED_TO_STREAM_ID, 0);
byte priority = (byte) httpMessage.headers().getInt(Names.PRIORITY, 0); byte priority = (byte) httpHeaders.getInt(Names.PRIORITY, 0);
String URL = httpHeaders.get(Names.URL); CharSequence scheme = httpHeaders.get(Names.SCHEME);
String scheme = httpHeaders.get(Names.SCHEME);
httpHeaders.remove(Names.STREAM_ID); httpHeaders.remove(Names.STREAM_ID);
httpHeaders.remove(Names.ASSOCIATED_TO_STREAM_ID); httpHeaders.remove(Names.ASSOCIATED_TO_STREAM_ID);
httpHeaders.remove(Names.PRIORITY); httpHeaders.remove(Names.PRIORITY);
httpHeaders.remove(Names.URL);
httpHeaders.remove(Names.SCHEME); httpHeaders.remove(Names.SCHEME);
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding // The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
@ -229,30 +215,18 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
httpHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING); httpHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING);
SpdySynStreamFrame spdySynStreamFrame = SpdySynStreamFrame spdySynStreamFrame =
new DefaultSpdySynStreamFrame(streamID, associatedToStreamId, priority); new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority);
// Unfold the first line of the message into name/value pairs // Unfold the first line of the message into name/value pairs
SpdyHeaders frameHeaders = spdySynStreamFrame.headers(); SpdyHeaders frameHeaders = spdySynStreamFrame.headers();
if (httpMessage instanceof FullHttpRequest) { frameHeaders.setObject(METHOD, httpRequest.method());
HttpRequest httpRequest = (HttpRequest) httpMessage; frameHeaders.set(PATH, httpRequest.uri());
frameHeaders.setObject(METHOD, httpRequest.method()); frameHeaders.setObject(VERSION, httpRequest.protocolVersion());
frameHeaders.set(PATH, httpRequest.uri());
frameHeaders.setObject(VERSION, httpMessage.protocolVersion());
}
if (httpMessage instanceof HttpResponse) {
HttpResponse httpResponse = (HttpResponse) httpMessage;
frameHeaders.setInt(STATUS, httpResponse.status().code());
frameHeaders.set(PATH, URL);
frameHeaders.setObject(VERSION, httpMessage.protocolVersion());
spdySynStreamFrame.setUnidirectional(true);
}
// Replace the HTTP host header with the SPDY host header // Replace the HTTP host header with the SPDY host header
if (spdyVersion >= 3) { CharSequence host = httpHeaders.get(HttpHeaderNames.HOST);
String host = httpMessage.headers().get(HttpHeaderNames.HOST); httpHeaders.remove(HttpHeaderNames.HOST);
httpHeaders.remove(HttpHeaderNames.HOST); frameHeaders.set(HOST, host);
frameHeaders.set(HOST, host);
}
// Set the SPDY scheme header // Set the SPDY scheme header
if (scheme == null) { if (scheme == null) {
@ -265,16 +239,20 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
frameHeaders.add(entry.getKey(), entry.getValue()); frameHeaders.add(entry.getKey(), entry.getValue());
} }
currentStreamId = spdySynStreamFrame.streamId(); currentStreamId = spdySynStreamFrame.streamId();
spdySynStreamFrame.setLast(isLast(httpMessage)); if (associatedToStreamId == 0) {
spdySynStreamFrame.setLast(isLast(httpRequest));
} else {
spdySynStreamFrame.setUnidirectional(true);
}
return spdySynStreamFrame; return spdySynStreamFrame;
} }
private SpdySynReplyFrame createSynReplyFrame(HttpResponse httpResponse) @SuppressWarnings("deprecation")
throws Exception { private SpdyHeadersFrame createHeadersFrame(HttpResponse httpResponse) throws Exception {
// Get the Stream-ID from the headers // Get the Stream-ID from the headers
final HttpHeaders httpHeaders = httpResponse.headers(); final HttpHeaders httpHeaders = httpResponse.headers();
int streamID = httpResponse.headers().getInt(Names.STREAM_ID); int streamId = httpHeaders.getInt(Names.STREAM_ID);
httpHeaders.remove(Names.STREAM_ID); httpHeaders.remove(Names.STREAM_ID);
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding // The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
@ -284,21 +262,26 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
httpHeaders.remove("Proxy-Connection"); httpHeaders.remove("Proxy-Connection");
httpHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING); httpHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING);
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID); SpdyHeadersFrame spdyHeadersFrame;
SpdyHeaders frameHeaders = spdySynReplyFrame.headers(); 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 // Unfold the first line of the response into name/value pairs
frameHeaders.setInt(STATUS, httpResponse.status().code()); frameHeaders.setInt(STATUS, httpResponse.status().code());
frameHeaders.setObject(VERSION, httpResponse.protocolVersion()); frameHeaders.setObject(VERSION, httpResponse.protocolVersion());
// Transfer the remaining HTTP headers // Transfer the remaining HTTP headers
for (Map.Entry<String, String> entry: httpHeaders) { for (Map.Entry<String, String> entry: httpHeaders) {
spdySynReplyFrame.headers().add(entry.getKey(), entry.getValue()); spdyHeadersFrame.headers().add(entry.getKey(), entry.getValue());
} }
currentStreamId = streamID; currentStreamId = streamId;
spdySynReplyFrame.setLast(isLast(httpResponse)); spdyHeadersFrame.setLast(isLast(httpResponse));
return spdySynReplyFrame; return spdyHeadersFrame;
} }
/** /**

View File

@ -39,10 +39,6 @@ public final class SpdyHttpHeaders {
* {@code "X-SPDY-Priority"} * {@code "X-SPDY-Priority"}
*/ */
public static final AsciiString PRIORITY = new AsciiString("X-SPDY-Priority"); public static final AsciiString PRIORITY = new AsciiString("X-SPDY-Priority");
/**
* {@code "X-SPDY-URL"}
*/
public static final AsciiString URL = new AsciiString("X-SPDY-URL");
/** /**
* {@code "X-SPDY-Scheme"} * {@code "X-SPDY-Scheme"}
*/ */