blob: 54f2e50431e1cad579abfd50f44d223f21311570 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.coyote.http2;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.ContinueResponseTiming;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.apache.tomcat.util.http.Method;
/**
* Unit tests for Section 8.1 of <a href="https://tools.ietf.org/html/rfc7540">RFC 7540</a>. <br>
* The order of tests in this class is aligned with the order of the examples in the RFC.
*/
public class TestHttp2Section_8_1 extends Http2TestBase {
@Test
public void testPostWithTrailerHeaders() throws Exception {
doTestPostWithTrailerHeaders(true);
}
@Test
public void testPostWithTrailerHeadersBlocked() throws Exception {
doTestPostWithTrailerHeaders(false);
}
private void doTestPostWithTrailerHeaders(boolean allowTrailerHeader) throws Exception {
http2Connect();
if (allowTrailerHeader) {
((AbstractHttp11Protocol<?>) http2Protocol.getHttp11Protocol())
.setAllowedTrailerHeaders(TRAILER_HEADER_NAME);
}
// Disable overhead protection for window update as it breaks some tests
http2Protocol.setOverheadWindowUpdateThreshold(0);
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, true, 3);
// Write the headers
writeFrame(headersFrameHeader, headersPayload);
// Body
writeFrame(dataFrameHeader, dataPayload);
sendSimpleGetRequest(5);
// Trailers
buildTrailerHeaders(trailerFrameHeader, trailerPayload, 3);
writeFrame(trailerFrameHeader, trailerPayload);
parser.readFrame();
parser.readFrame();
parser.readFrame();
parser.readFrame();
parser.readFrame();
parser.readFrame();
String len;
if (allowTrailerHeader) {
len = Integer.toString(256 + TRAILER_HEADER_VALUE.length());
} else {
len = "256";
}
/*
* There is no guaranteed order between stream 3 and stream 5. It all depends on server side timing. Also need
* to ensure no unexpected frames are received.
*/
String stream0WindowSize = "0-WindowSize-[256]\n";
String stream3WindowSize = "3-WindowSize-[256]\n";
String stream3Headers = "3-HeadersStart\n" + "3-Header-[:status]-[200]\n" +
"3-Header-[content-length]-[" + len + "]\n" + "3-Header-[date]-[" + DEFAULT_DATE + "]\n" +
"3-HeadersEnd\n";
String stream3Body = "3-Body-" + len + "\n" + "3-EndOfStream\n";
String stream5Headers = "5-HeadersStart\n" + "5-Header-[:status]-[200]\n" +
"5-Header-[content-type]-[application/octet-stream]\n" + "5-Header-[content-length]-[8192]\n" +
"5-Header-[date]-[" + DEFAULT_DATE + "]\n" + "5-HeadersEnd\n";
String stream5Body = "5-Body-8192\n" + "5-EndOfStream\n";
String trace = output.getTrace();
System.out.println(trace);
Assert.assertTrue(trace, trace.contains(stream0WindowSize));
Assert.assertTrue(trace, trace.contains(stream3WindowSize));
Assert.assertTrue(trace, trace.contains(stream3Headers));
Assert.assertTrue(trace, trace.contains(stream3Body));
Assert.assertTrue(trace, trace.contains(stream5Headers));
Assert.assertTrue(trace, trace.contains(stream5Body));
Assert.assertEquals("Unexpected data in trace - " + trace, stream0WindowSize.length() +
stream3WindowSize.length() + stream3Headers.length() + stream3Body.length() + stream5Headers.length() +
stream5Body.length(), trace.length());
}
@Test
public void testSendAckWithDefaultPolicy() throws Exception {
testSendAck();
}
@Test
public void testSendAckWithImmediatelyPolicy() throws Exception {
setContinueHandlingResponsePolicy(ContinueResponseTiming.IMMEDIATELY);
testSendAck();
}
@Test
public void testSendAckWithOnRequestBodyReadPolicy() throws Exception {
setContinueHandlingResponsePolicy(ContinueResponseTiming.ON_REQUEST_BODY_READ);
testSendAck();
}
public void setContinueHandlingResponsePolicy(ContinueResponseTiming policy) throws Exception {
final Tomcat tomcat = getTomcatInstance();
final Connector connector = tomcat.getConnector();
connector.setProperty("continueHandlingResponsePolicy", policy.toString());
}
@Test
public void testSendAck() throws Exception {
// makes a request that expects a 100 Continue response and verifies
// that the 100 Continue response is received. This does not check
// that the correct ContinueHandlingResponsePolicy was followed, just
// that a 100 Continue response is received. The unit tests for
// Request verify that the various policies are implemented.
http2Connect();
// Disable overhead protection for window update as it breaks some tests
http2Protocol.setOverheadWindowUpdateThreshold(0);
byte[] headersFrameHeader = new byte[9];
ByteBuffer headersPayload = ByteBuffer.allocate(128);
byte[] dataFrameHeader = new byte[9];
ByteBuffer dataPayload = ByteBuffer.allocate(256);
buildPostRequest(headersFrameHeader, headersPayload, true, null, -1, "/simple", dataFrameHeader, dataPayload,
null, false, 3);
// Write the headers
writeFrame(headersFrameHeader, headersPayload);
parser.readFrame();
Assert.assertEquals("3-HeadersStart\n" + "3-Header-[:status]-[100]\n" + "3-HeadersEnd\n", output.getTrace());
output.clearTrace();
// Write the body
writeFrame(dataFrameHeader, dataPayload);
parser.readFrame();
parser.readFrame();
parser.readFrame();
parser.readFrame();
Assert.assertEquals("0-WindowSize-[256]\n" + "3-WindowSize-[256]\n" + "3-HeadersStart\n" +
"3-Header-[:status]-[200]\n" + "3-Header-[content-length]-[256]\n" + "3-Header-[date]-[" +
DEFAULT_DATE + "]\n" + "3-HeadersEnd\n" + "3-Body-256\n" + "3-EndOfStream\n", output.getTrace());
}
@Test
public void testUndefinedPseudoHeader() throws Exception {
http2Connect();
List<Header> headers = new ArrayList<>(5);
headers.add(new Header(":method", Method.GET));
headers.add(new Header(":scheme", "http"));
headers.add(new Header(":path", "/simple"));
headers.add(new Header(":authority", "localhost:" + getPort()));
headers.add(new Header(":foo", "bar"));
doInvalidPseudoHeaderTest(headers);
}
@Test
public void testInvalidPseudoHeader() throws Exception {
http2Connect();
List<Header> headers = new ArrayList<>(5);
headers.add(new Header(":method", Method.GET));
headers.add(new Header(":scheme", "http"));
headers.add(new Header(":path", "/simple"));
headers.add(new Header(":authority", "localhost:" + getPort()));
headers.add(new Header(":status", "200"));
doInvalidPseudoHeaderTest(headers);
}
@Test
public void testPseudoHeaderOrder() throws Exception {
// Need to do this in two frames because HPACK encoder automatically
// re-orders fields
http2Connect();
List<Header> headers = new ArrayList<>(4);
headers.add(new Header(":method", Method.GET));
headers.add(new Header(":scheme", "http"));
headers.add(new Header(":path", "/simple"));
headers.add(new Header("x-test", "test"));
byte[] headersFrameHeader = new byte[9];
ByteBuffer headersPayload = ByteBuffer.allocate(128);
buildSimpleGetRequestPart1(headersFrameHeader, headersPayload, headers, 3);
writeFrame(headersFrameHeader, headersPayload);
headers.clear();
headers.add(new Header(":authority", "localhost:" + getPort()));
headersPayload.clear();
buildSimpleGetRequestPart2(headersFrameHeader, headersPayload, headers, 3);
writeFrame(headersFrameHeader, headersPayload);
parser.readFrame();
Assert.assertEquals("3-RST-[1]\n", output.getTrace());
}
@Test
public void testHostHeaderValid() throws Exception {
http2Connect();
List<Header> headers = new ArrayList<>(4);
headers.add(new Header(":method", Method.GET));
headers.add(new Header(":scheme", "http"));
headers.add(new Header(":path", "/simple"));
headers.add(new Header("host", "localhost:" + getPort()));
byte[] headersFrameHeader = new byte[9];
ByteBuffer headersPayload = ByteBuffer.allocate(128);
buildGetRequest(headersFrameHeader, headersPayload, null, headers, 3);
writeFrame(headersFrameHeader, headersPayload);
parser.readFrame();
String trace = output.getTrace();
Assert.assertTrue(trace, trace.contains("3-Header-[:status]-[200]"));
}
@Test
public void testHostHeaderDuplicate() throws Exception {
http2Connect();
List<Header> headers = new ArrayList<>(4);
headers.add(new Header(":method", Method.GET));
headers.add(new Header(":scheme", "http"));
headers.add(new Header(":path", "/simple"));
headers.add(new Header("host", "localhost:" + getPort()));
headers.add(new Header("host", "localhost:" + getPort()));
byte[] headersFrameHeader = new byte[9];
ByteBuffer headersPayload = ByteBuffer.allocate(128);
buildGetRequest(headersFrameHeader, headersPayload, null, headers, 3);
writeFrame(headersFrameHeader, headersPayload);
parser.readFrame();
String trace = output.getTrace();
Assert.assertTrue(trace, trace.contains("0-Goaway-[1]-[9]"));
}
@Test
public void testHostHeaderConsistent() throws Exception {
http2Connect();
List<Header> headers = new ArrayList<>(4);
headers.add(new Header(":method", Method.GET));
headers.add(new Header(":scheme", "http"));
headers.add(new Header(":authority", "localhost:" + getPort()));
headers.add(new Header(":path", "/simple"));
headers.add(new Header("host", "localhost:" + getPort()));
byte[] headersFrameHeader = new byte[9];
ByteBuffer headersPayload = ByteBuffer.allocate(128);
buildGetRequest(headersFrameHeader, headersPayload, null, headers, 3);
writeFrame(headersFrameHeader, headersPayload);
parser.readFrame();
String trace = output.getTrace();
Assert.assertTrue(trace, trace.contains("3-Header-[:status]-[200]"));
}
@Test
public void testHostHeaderConsistentNoPort() throws Exception {
http2Connect();
List<Header> headers = new ArrayList<>(4);
headers.add(new Header(":method", Method.GET));
headers.add(new Header(":scheme", "http"));
headers.add(new Header(":authority", "localhost"));
headers.add(new Header(":path", "/simple"));
headers.add(new Header("host", "localhost"));
byte[] headersFrameHeader = new byte[9];
ByteBuffer headersPayload = ByteBuffer.allocate(128);
buildGetRequest(headersFrameHeader, headersPayload, null, headers, 3);
writeFrame(headersFrameHeader, headersPayload);
parser.readFrame();
String trace = output.getTrace();
Assert.assertTrue(trace, trace.contains("3-Header-[:status]-[200]"));
}
@Test
public void testHostHeaderInconsistent01() throws Exception {
http2Connect();
doTestHostHeaderInconsistent("localhost:" + getPort(), "otherhost:" + getPort());
}
@Test
public void testHostHeaderInconsistent02() throws Exception {
http2Connect();
doTestHostHeaderInconsistent("localhost", "otherhost");
}
@Test
public void testHostHeaderInconsistent03() throws Exception {
http2Connect();
doTestHostHeaderInconsistent("localhost:" + getPort(), "localhost");
}
@Test
public void testHostHeaderInconsistent04() throws Exception {
http2Connect();
doTestHostHeaderInconsistent("localhost", "localhost:" + getPort());
}
@Test
public void testHostHeaderInconsistent05() throws Exception {
http2Connect();
doTestHostHeaderInconsistent("localhost:" + getPort(), "otherhost:" + (getPort() + 1));
}
private void doTestHostHeaderInconsistent(String authority, String host) throws Exception {
List<Header> headers = new ArrayList<>(4);
headers.add(new Header(":method", Method.GET));
headers.add(new Header(":scheme", "http"));
headers.add(new Header(":authority", authority));
headers.add(new Header(":path", "/simple"));
headers.add(new Header("host", host));
byte[] headersFrameHeader = new byte[9];
ByteBuffer headersPayload = ByteBuffer.allocate(128);
buildGetRequest(headersFrameHeader, headersPayload, null, headers, 3);
writeFrame(headersFrameHeader, headersPayload);
parser.readFrame();
String trace = output.getTrace();
Assert.assertTrue(trace, trace.contains("0-Goaway-[1]-[9]"));
}
private void doInvalidPseudoHeaderTest(List<Header> headers) throws Exception {
doInvalidPseudoHeaderTest(headers, "3-RST-[1]\n");
}
private void doInvalidPseudoHeaderTest(List<Header> headers, String expected) throws Exception {
byte[] headersFrameHeader = new byte[9];
ByteBuffer headersPayload = ByteBuffer.allocate(128);
buildGetRequest(headersFrameHeader, headersPayload, null, headers, 3);
// Write the headers
writeFrame(headersFrameHeader, headersPayload);
parser.readFrame();
String trace = output.getTrace();
if (trace.length() > expected.length()) {
trace = trace.substring(0, expected.length());
}
Assert.assertEquals(output.getTrace(), expected, trace);
}
@Test
public void testSchemeHeaderValid() throws Exception {
http2Connect();
List<Header> headers = new ArrayList<>(4);
headers.add(new Header(":method", Method.GET));
headers.add(new Header(":scheme", "abcd"));
headers.add(new Header(":authority", "localhost:" + getPort()));
headers.add(new Header(":path", "/simple"));
headers.add(new Header("host", "localhost:" + getPort()));
byte[] headersFrameHeader = new byte[9];
ByteBuffer headersPayload = ByteBuffer.allocate(128);
buildGetRequest(headersFrameHeader, headersPayload, null, headers, 3);
writeFrame(headersFrameHeader, headersPayload);
parser.readFrame();
String trace = output.getTrace();
Assert.assertTrue(trace, trace.contains("3-Header-[:status]-[200]"));
}
@Test
public void testSchemeHeaderInvalid01() throws Exception {
http2Connect();
List<Header> headers = new ArrayList<>(4);
headers.add(new Header(":method", Method.GET));
headers.add(new Header(":scheme", "ab!cd"));
headers.add(new Header(":authority", "localhost:" + getPort()));
headers.add(new Header(":path", "/simple"));
headers.add(new Header("host", "localhost:" + getPort()));
doInvalidPseudoHeaderTest(headers, "3-HeadersStart\n3-Header-[:status]-[400]\n");
}
@Test
public void testSchemeHeaderInvalid02() throws Exception {
http2Connect();
List<Header> headers = new ArrayList<>(4);
headers.add(new Header(":method", Method.GET));
headers.add(new Header(":scheme", ""));
headers.add(new Header(":authority", "localhost:" + getPort()));
headers.add(new Header(":path", "/simple"));
headers.add(new Header("host", "localhost:" + getPort()));
doInvalidPseudoHeaderTest(headers, "3-HeadersStart\n3-Header-[:status]-[400]\n");
}
@Test
public void testSchemeHeaderMissing() throws Exception {
http2Connect();
List<Header> headers = new ArrayList<>(4);
headers.add(new Header(":method", Method.GET));
headers.add(new Header(":authority", "localhost:" + getPort()));
headers.add(new Header(":path", "/simple"));
headers.add(new Header("host", "localhost:" + getPort()));
doInvalidPseudoHeaderTest(headers, "0-Goaway-[3]-[1]-");
}
}