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.
This commit is contained in:
Jeff Pinner 2014-11-14 15:07:00 -08:00 committed by Norman Maurer
parent e09ffc7d60
commit 6f80fdcac4

View File

@ -137,7 +137,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
int associatedToStreamId = spdySynStreamFrame.associatedStreamId(); int associatedToStreamId = spdySynStreamFrame.associatedStreamId();
// If a client receives a SYN_STREAM with an Associated-To-Stream-ID of 0 // 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) { if (associatedToStreamId == 0) {
SpdyRstStreamFrame spdyRstStreamFrame = SpdyRstStreamFrame spdyRstStreamFrame =
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INVALID_STREAM); new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INVALID_STREAM);
@ -145,12 +145,10 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
return; return;
} }
CharSequence URL = spdySynStreamFrame.headers().get(PATH); // If a client receives a SYN_STREAM with isLast set,
spdySynStreamFrame.headers().remove(PATH); // reply with a RST_STREAM with error code PROTOCOL_ERROR
// (we only support pushed resources divided into two header blocks).
// If a client receives a SYN_STREAM without a 'url' header if (spdySynStreamFrame.isLast()) {
// it must reply with a RST_STREAM with error code PROTOCOL_ERROR
if (URL == null) {
SpdyRstStreamFrame spdyRstStreamFrame = SpdyRstStreamFrame spdyRstStreamFrame =
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR); new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
ctx.writeAndFlush(spdyRstStreamFrame); ctx.writeAndFlush(spdyRstStreamFrame);
@ -167,22 +165,15 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
} }
try { try {
FullHttpResponse httpResponseWithEntity = FullHttpRequest httpRequestWithEntity = createHttpRequest(spdyVersion, spdySynStreamFrame);
createHttpResponse(ctx, spdySynStreamFrame, validateHeaders);
// Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers // Set the Stream-ID, Associated-To-Stream-ID, iand Priority as headers
httpResponseWithEntity.headers().setInt(Names.STREAM_ID, streamId); httpRequestWithEntity.headers().setInt(Names.STREAM_ID, streamId);
httpResponseWithEntity.headers().setInt(Names.ASSOCIATED_TO_STREAM_ID, associatedToStreamId); httpRequestWithEntity.headers().setInt(Names.ASSOCIATED_TO_STREAM_ID, associatedToStreamId);
httpResponseWithEntity.headers().setInt(Names.PRIORITY, spdySynStreamFrame.priority()); httpRequestWithEntity.headers().setInt(Names.PRIORITY, spdySynStreamFrame.priority());
httpResponseWithEntity.headers().set(Names.URL, URL);
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) { } catch (Exception ignored) {
SpdyRstStreamFrame spdyRstStreamFrame = SpdyRstStreamFrame spdyRstStreamFrame =
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR); new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
@ -269,8 +260,40 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
int streamId = spdyHeadersFrame.streamId(); int streamId = spdyHeadersFrame.streamId();
FullHttpMessage fullHttpMessage = getMessage(streamId); FullHttpMessage fullHttpMessage = getMessage(streamId);
// If message is not in map discard HEADERS frame.
if (fullHttpMessage == null) { 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; return;
} }