Author: markt Date: Mon Jun 1 12:31:56 2015 New Revision: 1682885 URL: http://svn.apache.org/r1682885 Log: Refactor - put the test only code in the test classes - reorganise to make it easier to test various connection scenarios Implement a couple of TODO tests
Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_3_2.java Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java?rev=1682885&r1=1682884&r2=1682885&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java Mon Jun 1 12:31:56 2015 @@ -17,7 +17,6 @@ package org.apache.coyote.http2; import java.io.IOException; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.apache.juli.logging.Log; @@ -92,61 +91,6 @@ class Http2Parser { return true; } - - boolean readHttpUpgradeResponse() throws IOException { - // Only used by test code so safe to keep this just a little larger than - // we are expecting. - ByteBuffer data = ByteBuffer.allocate(128); - byte[] singleByte = new byte[1]; - // Looking for \r\n\r\n - int seen = 0; - while (seen < 4) { - input.fill(singleByte, true); - switch (seen) { - case 0: - case 2: { - if (singleByte[0] == '\r') { - seen++; - } else { - seen = 0; - } - break; - } - case 1: - case 3: { - if (singleByte[0] == '\n') { - seen++; - } else { - seen = 0; - } - break; - } - } - data.put(singleByte[0]); - } - - String response = new String(data.array(), data.arrayOffset(), - data.arrayOffset() + data.position(), StandardCharsets.ISO_8859_1); - - String[] responseLines = response.split("\r\n"); - - if (responseLines.length < 3) { - return false; - } - if (!responseLines[0].startsWith("HTTP/1.1 101")) { - return false; - } - // TODO: There may be other headers. - if (!responseLines[1].equals("Connection: Upgrade")) { - return false; - } - if (!responseLines[2].startsWith("Upgrade: h2c")) { - return false; - } - - return true; - } - /** * Interface that must be implemented by the source of data for the parser. Modified: tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java?rev=1682885&r1=1682884&r2=1682885&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java (original) +++ tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java Mon Jun 1 12:31:56 2015 @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import javax.net.SocketFactory; @@ -29,12 +30,13 @@ import javax.servlet.http.HttpServletReq import javax.servlet.http.HttpServletResponse; import org.junit.Assert; -import org.junit.Before; import org.apache.catalina.Context; +import org.apache.catalina.LifecycleException; import org.apache.catalina.connector.Connector; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.TomcatBaseTest; +import org.apache.coyote.http2.Http2Parser.Input; import org.apache.tomcat.util.codec.binary.Base64; @@ -52,31 +54,34 @@ public abstract class Http2TestBase exte } private Socket s; + protected Input input; protected Http2Parser parser; protected OutputStream os; - @Before - public void setup() throws Exception { - Tomcat tomcat = getTomcatInstance(); - - // Enable HTTP/2 - Connector connector = tomcat.getConnector(); + protected void enableHttp2() { + Connector connector = getTomcatInstance().getConnector(); Http2Protocol http2Protocol = new Http2Protocol(); // Short timeouts for now. May need to increase these for CI systems. http2Protocol.setReadTimeout(2000); http2Protocol.setKeepAliveTimeout(5000); http2Protocol.setWriteTimeout(2000); connector.addUpgradeProtocol(http2Protocol); + } + + + protected void configureAndStartWebApplication() throws LifecycleException { + Tomcat tomcat = getTomcatInstance(); - // Add a web application Context ctxt = tomcat.addContext("", null); Tomcat.addServlet(ctxt, "simple", new SimpleServlet()); ctxt.addServletMapping("/*", "simple"); - // Start the Tomcat instance tomcat.start(); + } + + protected void openClientConnection() throws IOException { // Open a connection s = SocketFactory.getDefault().createSocket("localhost", getPort()); s.setSoTimeout(30000); @@ -84,23 +89,89 @@ public abstract class Http2TestBase exte os = s.getOutputStream(); InputStream is = s.getInputStream(); - TestInput testInput = new TestInput(is); - parser = new Http2Parser(testInput); + input = new TestInput(is); + parser = new Http2Parser(input); } - protected void doHttpUpgrade() throws IOException { + protected void doHttpUpgrade(String upgrade, boolean validate) throws IOException { byte[] upgradeRequest = ("GET / HTTP/1.1\r\n" + "Host: localhost:" + getPort() + "\r\n" + "Connection: Upgrade, HTTP2-Settings\r\n" + - "Upgrade: h2c\r\n" + + "Upgrade: " + upgrade + "\r\n" + "HTTP2-Settings: "+ HTTP2_SETTINGS + "\r\n" + "\r\n").getBytes(StandardCharsets.ISO_8859_1); os.write(upgradeRequest); os.flush(); - Assert.assertTrue("Failed to read HTTP Upgrade response", parser.readHttpUpgradeResponse()); + if (validate) { + Assert.assertTrue("Failed to read HTTP Upgrade response", + readHttpUpgradeResponse()); + } + } + + + boolean readHttpUpgradeResponse() throws IOException { + String[] responseHeaders = readHttpResponseHeaders(); + + if (responseHeaders.length < 3) { + return false; + } + if (!responseHeaders[0].startsWith("HTTP/1.1 101")) { + return false; + } + // TODO: There may be other headers. + if (!responseHeaders[1].equals("Connection: Upgrade")) { + return false; + } + if (!responseHeaders[2].startsWith("Upgrade: h2c")) { + return false; + } + + return true; + } + + + String[] readHttpResponseHeaders() throws IOException { + // Only used by test code so safe to keep this just a little larger than + // we are expecting. + ByteBuffer data = ByteBuffer.allocate(256); + byte[] singleByte = new byte[1]; + // Looking for \r\n\r\n + int seen = 0; + while (seen < 4) { + input.fill(singleByte, true); + switch (seen) { + case 0: + case 2: { + if (singleByte[0] == '\r') { + seen++; + } else { + seen = 0; + } + break; + } + case 1: + case 3: { + if (singleByte[0] == '\n') { + seen++; + } else { + seen = 0; + } + break; + } + } + data.put(singleByte[0]); + } + + if (seen != 4) { + throw new IOException("End of headers not found"); + } + + String response = new String(data.array(), data.arrayOffset(), + data.arrayOffset() + data.position(), StandardCharsets.ISO_8859_1); + return response.split("\r\n"); } @@ -141,10 +212,15 @@ public abstract class Http2TestBase exte throws ServletException, IOException { // Generate content with a simple known format. resp.setContentType("application/octet-stream"); + + int count = 16 * 1024; + // Two bytes per entry + resp.setContentLengthLong(count * 2); + OutputStream os = resp.getOutputStream(); byte[] data = new byte[2]; // 1024 * 16 * 2 bytes = 32k of content. - for (int i = 0; i < 1024 * 16; i++) { + for (int i = 0; i < count; i++) { data[0] = (byte) (i & 0xFF); data[1] = (byte) ((i >> 8) & 0xFF); os.write(data); Modified: tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_3_2.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_3_2.java?rev=1682885&r1=1682884&r2=1682885&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_3_2.java (original) +++ tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_3_2.java Mon Jun 1 12:31:56 2015 @@ -18,7 +18,9 @@ package org.apache.coyote.http2; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Locale; +import org.junit.Assert; import org.junit.Test; /** @@ -35,13 +37,28 @@ public class TestHttp2Section_3_2 extend // TODO: Test initial requests with bodies of various sizes - // TODO: Test response with HTTP/2 is not enabled + @Test + public void testConnectionNoHttp2Support() throws Exception { + configureAndStartWebApplication(); + openClientConnection(); + doHttpUpgrade("h2c", false); + parseHttp11Response(); + } + + + @Test + public void testConnectionUpgradeWrongProtocol() throws Exception { + enableHttp2(); + configureAndStartWebApplication(); + openClientConnection(); + doHttpUpgrade("h2", false); + parseHttp11Response(); + } - // TODO: Test HTTP upgrade with h2 rather tan h2c @Test(timeout=10000) - public void testConnectionNoPreface() throws IOException { - doHttpUpgrade(); + public void testConnectionNoPreface() throws Exception { + setupAsFarAsUpgrade(); // If we don't send the preface the server should kill the connection. try { @@ -54,8 +71,8 @@ public class TestHttp2Section_3_2 extend @Test(timeout=10000) - public void testConnectionIncompletePrefaceStart() throws IOException { - doHttpUpgrade(); + public void testConnectionIncompletePrefaceStart() throws Exception { + setupAsFarAsUpgrade(); // If we send an incomplete preface the server should kill the // connection. @@ -71,8 +88,8 @@ public class TestHttp2Section_3_2 extend @Test(timeout=10000) - public void testConnectionInvalidPrefaceStart() throws IOException { - doHttpUpgrade(); + public void testConnectionInvalidPrefaceStart() throws Exception { + setupAsFarAsUpgrade(); // If we send an incomplete preface the server should kill the // connection. @@ -93,4 +110,34 @@ public class TestHttp2Section_3_2 extend // TODO: Test if client doesn't send SETTINGS as part of the preface // TODO: Test response is received on stream 1 + + + private void setupAsFarAsUpgrade() throws Exception { + enableHttp2(); + configureAndStartWebApplication(); + openClientConnection(); + doHttpUpgrade("h2c", true); + } + + + private void parseHttp11Response() throws IOException { + String[] responseHeaders = readHttpResponseHeaders(); + Assert.assertTrue(responseHeaders[0], responseHeaders[0].startsWith("HTTP/1.1 200")); + + // Find the content length (chunked responses not handled) + for (int i = 1; i < responseHeaders.length; i++) { + if (responseHeaders[i].toLowerCase(Locale.ENGLISH).startsWith("content-length")) { + String cl = responseHeaders[i]; + int pos = cl.indexOf(':'); + if (pos == -1) { + throw new IOException("Invalid: [" + cl + "]"); + } + int len = Integer.parseInt(cl.substring(pos + 1).trim()); + byte[] content = new byte[len]; + input.fill(content, true); + return; + } + } + throw new IOException("No content-length"); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org