/*
* 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.nio.charset.StandardCharsets;
import org.junit.Assert;
import org.junit.Test;
import org.apache.catalina.Context;
import org.apache.catalina.startup.Tomcat;
import org.apache.tomcat.util.http.Method;
public class TestHttp2UpgradeHandler extends Http2TestBase {
// https://bz.apache.org/bugzilla/show_bug.cgi?id=60970
@Test
public void testLargeHeader() throws Exception {
enableHttp2();
Tomcat tomcat = getTomcatInstance();
Context ctxt = getProgrammaticRootContext();
Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
ctxt.addServletMappingDecoded("/simple", "simple");
Tomcat.addServlet(ctxt, "large", new LargeHeaderServlet());
ctxt.addServletMappingDecoded("/large", "large");
tomcat.start();
openClientConnection();
doHttpUpgrade();
sendClientPreface();
validateHttp2InitialResponse();
byte[] frameHeader = new byte[9];
ByteBuffer headersPayload = ByteBuffer.allocate(128);
buildGetRequest(frameHeader, headersPayload, null, 3, "/large");
writeFrame(frameHeader, headersPayload);
// Headers
parser.readFrame();
parser.readFrame();
// Body
parser.readFrame();
Assert.assertEquals("3-HeadersStart " + "3-Header-[:status]-[200] " + "3-Header-[x-ignore]-[...] " +
"3-Header-[content-type]-[text/plain;charset=UTF-8] " + "3-Header-[content-length]-[2] " +
"3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT] " + "3-HeadersEnd " + "3-Body-2 " +
"3-EndOfStream ", output.getTrace());
}
@Test
public void testUpgradeWithRequestBodyGet() throws Exception {
doTestUpgradeWithRequestBody(false, false, false);
}
@Test
public void testUpgradeWithRequestBodyGetTooBig() throws Exception {
doTestUpgradeWithRequestBody(false, false, true);
}
@Test
public void testUpgradeWithRequestBodyPost() throws Exception {
doTestUpgradeWithRequestBody(true, false, false);
}
@Test
public void testUpgradeWithRequestBodyPostTooBig() throws Exception {
doTestUpgradeWithRequestBody(true, false, true);
}
@Test
public void testUpgradeWithRequestBodyGetReader() throws Exception {
doTestUpgradeWithRequestBody(false, true, false);
}
@Test
public void testUpgradeWithRequestBodyGetReaderTooBig() throws Exception {
doTestUpgradeWithRequestBody(false, true, true);
}
@Test
public void testUpgradeWithRequestBodyPostReader() throws Exception {
doTestUpgradeWithRequestBody(true, true, false);
}
@Test
public void testUpgradeWithRequestBodyPostReaderTooBig() throws Exception {
doTestUpgradeWithRequestBody(true, true, true);
}
private void doTestUpgradeWithRequestBody(boolean usePost, boolean useReader, boolean tooBig) throws Exception {
enableHttp2();
Tomcat tomcat = getTomcatInstance();
Context ctxt = getProgrammaticRootContext();
Tomcat.addServlet(ctxt, "ReadRequestBodyServlet", new ReadRequestBodyServlet());
ctxt.addServletMappingDecoded("/", "ReadRequestBodyServlet");
if (tooBig) {
// Reduce maxSavePostSize rather than use a larger request body
tomcat.getConnector().setProperty("maxSavePostSize", "10");
}
tomcat.start();
openClientConnection();
byte[] upgradeRequest = ((usePost ? Method.POST : Method.GET) + " /" + (useReader ? "?useReader=true " : " ") +
"HTTP/1.1 " + "Host: localhost:" + getPort() + " " + "Content-Length: 18 " +
"Connection: Upgrade,HTTP2-Settings " + "Upgrade: h2c " + EMPTY_HTTP2_SETTINGS_HEADER + " " +
"Small request body").getBytes(StandardCharsets.ISO_8859_1);
os.write(upgradeRequest);
os.flush();
if (tooBig) {
String[] responseHeaders = readHttpResponseHeaders();
Assert.assertNotNull(responseHeaders);
Assert.assertNotEquals(0, responseHeaders.length);
Assert.assertEquals("HTTP/1.1 413 ", responseHeaders[0]);
} else {
Assert.assertTrue("Failed to read HTTP Upgrade response", readHttpUpgradeResponse());
sendClientPreface();
// - 101 response acts as acknowledgement of the HTTP2-Settings header
// Need to read 5 frames
// - settings (server settings - must be first)
// - settings ack (for the settings frame in the client preface)
// - ping
// - headers (for response)
// - data (for response body)
parser.readFrame();
parser.readFrame();
parser.readFrame();
parser.readFrame();
parser.readFrame();
Assert.assertEquals("0-Settings-[3]-[200] " + "0-Settings-End " + "0-Settings-Ack " +
"0-Ping-[0,0,0,0,0,0,0,1] " + "1-HeadersStart " + "1-Header-[:status]-[200] " +
"1-Header-[content-type]-[text/plain;charset=UTF-8] " + "1-Header-[content-length]-[39] " +
"1-Header-[date]-[" + DEFAULT_DATE + "] " + "1-HeadersEnd " + "1-Body-39 " + "1-EndOfStream ",
output.getTrace());
}
}
@Test
public void testActiveConnectionCountAndClientTimeout() throws Exception {
enableHttp2(2, false, 10000, 10000, 4000, 2000, 2000);
Tomcat tomcat = getTomcatInstance();
Context ctxt = getProgrammaticRootContext();
Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
ctxt.addServletMappingDecoded("/simple", "simple");
tomcat.start();
openClientConnection();
doHttpUpgrade();
sendClientPreface();
validateHttp2InitialResponse(2);
byte[] frameHeader = new byte[9];
ByteBuffer headersPayload = ByteBuffer.allocate(128);
byte[] dataFrameHeader = new byte[9];
ByteBuffer dataFramePayload = ByteBuffer.allocate(128);
// Should be able to make more than 2 requests even if they timeout
// since they should be removed from active connections once they
// timeout
for (int stream = 3; stream < 8; stream += 2) {
// Don't write the body. Allow the read to timeout.
buildPostRequest(frameHeader, headersPayload, false, dataFrameHeader, dataFramePayload, null, stream);
writeFrame(frameHeader, headersPayload);
// 400 response (triggered by IOException trying to read body that never arrived)
parser.readFrame();
Assert.assertTrue(output.getTrace(),
output.getTrace().startsWith(stream + "-HeadersStart " + stream + "-Header-[:status]-[400] "));
output.clearTrace();
// reset frame
parser.readFrame();
Assert.assertEquals(stream + "-RST-[11] ", output.getTrace());
output.clearTrace();
// Prepare buffers for re-use
headersPayload.clear();
dataFramePayload.clear();
}
}
}