Skip to content

Commit

Permalink
Add initial support for trailer headers
Browse files Browse the repository at this point in the history
git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1765589 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
markt-asf committed Oct 19, 2016
1 parent 90dd015 commit 02e1730
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 5 deletions.
5 changes: 3 additions & 2 deletions java/org/apache/coyote/http2/Http2UpgradeHandler.java
Expand Up @@ -1247,8 +1247,9 @@ public void headersEnd(int streamId) throws ConnectionException {
setMaxProcessedStream(streamId);
Stream stream = getStream(streamId, connectionState.get().isNewStreamAllowed());
if (stream != null && stream.isActive()) {
stream.receivedEndOfHeaders();
processStreamOnContainerThread(stream);
if (stream.receivedEndOfHeaders()) {
processStreamOnContainerThread(stream);
}
}
}

Expand Down
6 changes: 5 additions & 1 deletion java/org/apache/coyote/http2/Stream.java
Expand Up @@ -293,13 +293,14 @@ public void validateHeaders() throws StreamException {
}


final void receivedEndOfHeaders() {
final boolean receivedEndOfHeaders() {
// Cookie headers need to be concatenated into a single header
// See RFC 7540 8.1.2.5
// Can only do this once the headers are fully received
if (cookieHeader != null) {
coyoteRequest.getMimeHeaders().addValue("cookie").setString(cookieHeader.toString());
}
return headerState == HEADER_STATE_REGULAR || headerState == HEADER_STATE_PSEUDO;
}


Expand Down Expand Up @@ -362,6 +363,9 @@ final void receivedStartOfHeaders() {


final void receivedEndOfStream() {
synchronized (inputBuffer) {
inputBuffer.notifyAll();
}
state.recievedEndOfStream();
}

Expand Down
38 changes: 36 additions & 2 deletions test/org/apache/coyote/http2/Http2TestBase.java
Expand Up @@ -69,6 +69,8 @@ public abstract class Http2TestBase extends TomcatBaseTest {
EMPTY_HTTP2_SETTINGS_HEADER = "HTTP2-Settings: " + Base64.encodeBase64String(empty) + "\r\n";
}

private static final String TRAILER_HEADER_NAME = "X-TrailerTest";

private Socket s;
protected HpackEncoder hpackEncoder;
protected Input input;
Expand Down Expand Up @@ -294,6 +296,13 @@ protected void sendSimplePostRequest(int streamId, byte[] padding, boolean write
protected void buildPostRequest(byte[] headersFrameHeader, ByteBuffer headersPayload,
boolean useExpectation, byte[] dataFrameHeader, ByteBuffer dataPayload, byte[] padding,
int streamId) {
buildPostRequest(headersFrameHeader, headersPayload, useExpectation, dataFrameHeader,
dataPayload, padding, null, null, streamId);
}

protected void buildPostRequest(byte[] headersFrameHeader, ByteBuffer headersPayload,
boolean useExpectation, byte[] dataFrameHeader, ByteBuffer dataPayload, byte[] padding,
byte[] trailersFrameHeader, ByteBuffer trailersPayload, int streamId) {
MimeHeaders headers = new MimeHeaders();
headers.addValue(":method").setString("POST");
headers.addValue(":path").setString("/simple");
Expand Down Expand Up @@ -332,12 +341,31 @@ protected void buildPostRequest(byte[] headersFrameHeader, ByteBuffer headersPay
ByteUtil.setThreeBytes(dataFrameHeader, 0, dataPayload.limit());
// Data is type 0
// Flags: End of stream 1, Padding 8
if (padding == null) {
if (trailersPayload == null) {
dataFrameHeader[4] = 0x01;
} else {
dataFrameHeader[4] = 0x09;
dataFrameHeader[4] = 0x00;
}
if (padding != null) {
dataFrameHeader[4] += 0x08;
}
ByteUtil.set31Bits(dataFrameHeader, 5, streamId);

// Trailers
if (trailersPayload != null) {
MimeHeaders trailerHeaders = new MimeHeaders();
trailerHeaders.addValue(TRAILER_HEADER_NAME).setString("xxxx");
hpackEncoder.encode(trailerHeaders, trailersPayload);

trailersPayload.flip();

ByteUtil.setThreeBytes(trailersFrameHeader, 0, trailersPayload.limit());
trailersFrameHeader[3] = FrameType.HEADERS.getIdByte();
// Flags. end of headers (0x04) and end of stream (0x01)
trailersFrameHeader[4] = 0x05;
// Stream id
ByteUtil.set31Bits(trailersFrameHeader, 5, streamId);
}
}


Expand Down Expand Up @@ -1049,6 +1077,12 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp)

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
IOTools.flow(bais, resp.getOutputStream());

// Check for trailer headers
String trailerValue = req.getHeader(TRAILER_HEADER_NAME);
if (trailerValue != null) {
resp.getOutputStream().write(trailerValue.getBytes(StandardCharsets.UTF_8));
}
}
}

Expand Down
38 changes: 38 additions & 0 deletions test/org/apache/coyote/http2/TestHttp2Section_8_1.java
Expand Up @@ -32,6 +32,44 @@
*/
public class TestHttp2Section_8_1 extends Http2TestBase {

@Test
public void testPostWithTrailerHeaders() throws Exception {
http2Connect();

byte[] headersFrameHeader = new byte[9];
ByteBuffer headersPayload = ByteBuffer.allocate(128);
byte[] dataFrameHeader = new byte[9];
ByteBuffer dataPayload = ByteBuffer.allocate(256);
byte[] trailerFrameHeader = new byte[9];
ByteBuffer trailerPayload = ByteBuffer.allocate(256);

buildPostRequest(headersFrameHeader, headersPayload, false, dataFrameHeader, dataPayload,
null, trailerFrameHeader, trailerPayload, 3);

// Write the headers
writeFrame(headersFrameHeader, headersPayload);
// Body
writeFrame(dataFrameHeader, dataPayload);
// Trailers
writeFrame(trailerFrameHeader, trailerPayload);

parser.readFrame(true);
parser.readFrame(true);
parser.readFrame(true);
parser.readFrame(true);

Assert.assertEquals("0-WindowSize-[256]\n" +
"3-WindowSize-[256]\n" +
"3-HeadersStart\n" +
"3-Header-[:status]-[200]\n" +
"3-Header-[date]-["+ DEFAULT_DATE + "]\n" +
"3-HeadersEnd\n" +
"3-Body-260\n" +
"3-EndOfStream\n",
output.getTrace());
}


@Test
public void testSendAck() throws Exception {
http2Connect();
Expand Down

0 comments on commit 02e1730

Please sign in to comment.