init
This commit is contained in:
173
test/org/apache/catalina/connector/TestConnector.java
Normal file
173
test/org/apache/catalina/connector/TestConnector.java
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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.catalina.connector;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.servlets.DefaultServlet;
|
||||
import org.apache.catalina.servlets.WebdavServlet;
|
||||
import org.apache.catalina.startup.TesterServlet;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
/**
|
||||
* Test cases for {@link Connector}.
|
||||
*/
|
||||
public class TestConnector extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testStop() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Wrapper w =
|
||||
Tomcat.addServlet(root, "tester", new TesterServlet());
|
||||
w.setAsyncSupported(true);
|
||||
root.addServletMappingDecoded("/", "tester");
|
||||
|
||||
Connector connector = tomcat.getConnector();
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/", bc, null, null);
|
||||
|
||||
Assert.assertEquals(200, rc);
|
||||
Assert.assertEquals("OK", bc.toString());
|
||||
|
||||
rc = -1;
|
||||
bc.recycle();
|
||||
|
||||
connector.stop();
|
||||
|
||||
try {
|
||||
rc = getUrl("http://localhost:" + getPort() + "/", bc, 1000,
|
||||
null, null);
|
||||
} catch (SocketTimeoutException ste) {
|
||||
// May also see this with NIO
|
||||
// Make sure the test passes if we do
|
||||
rc = 503;
|
||||
}
|
||||
Assert.assertEquals(503, rc);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPort() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Connector connector1 = tomcat.getConnector();
|
||||
connector1.setPort(0);
|
||||
|
||||
Connector connector2 = new Connector();
|
||||
connector2.setPort(0);
|
||||
|
||||
tomcat.getService().addConnector(connector2);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
int localPort1 = connector1.getLocalPort();
|
||||
int localPort2 = connector2.getLocalPort();
|
||||
|
||||
Assert.assertTrue(localPort1 > 0);
|
||||
Assert.assertTrue(localPort2 > 0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceAllowedDefault() throws Exception {
|
||||
doTestTrace(new DefaultServlet(), true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceNotAllowedDefault() throws Exception {
|
||||
doTestTrace(new DefaultServlet(), false);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceAllowedWebDav() throws Exception {
|
||||
doTestTrace(new WebdavServlet(), true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceNotAllowedWebDav() throws Exception {
|
||||
doTestTrace(new WebdavServlet(), false);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceAllowedCustom() throws Exception {
|
||||
doTestTrace(new TesterServlet(), true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTraceNotAllowedCustom() throws Exception {
|
||||
doTestTrace(new TesterServlet(), false);
|
||||
}
|
||||
|
||||
|
||||
private void doTestTrace(Servlet servlet, boolean allowTrace) throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
File appDir = new File("test/webapp");
|
||||
Context root = tomcat.addContext("", appDir.getAbsolutePath());
|
||||
Tomcat.addServlet(root, "default", servlet);
|
||||
root.addServletMappingDecoded("/", "default");
|
||||
|
||||
Connector connector = tomcat.getConnector();
|
||||
connector.setAllowTrace(allowTrace);
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
Map<String,List<String>> respHeaders = new HashMap<>();
|
||||
int rc = methodUrl("http://localhost:" + getPort() + "/index.html",
|
||||
bc, 30000, null, respHeaders, "OPTIONS");
|
||||
|
||||
Assert.assertEquals(200, rc);
|
||||
|
||||
boolean foundTrace = false;
|
||||
for (String header : respHeaders.get("Allow")) {
|
||||
if (header.contains("TRACE")) {
|
||||
foundTrace = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowTrace) {
|
||||
Assert.assertTrue(foundTrace);
|
||||
} else {
|
||||
Assert.assertFalse(foundTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
413
test/org/apache/catalina/connector/TestCoyoteAdapter.java
Normal file
413
test/org/apache/catalina/connector/TestCoyoteAdapter.java
Normal file
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* 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.catalina.connector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.startup.SimpleHttpClient;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.buf.MessageBytes;
|
||||
|
||||
public class TestCoyoteAdapter extends TomcatBaseTest {
|
||||
|
||||
public static final String TEXT_8K;
|
||||
public static final byte[] BYTES_8K;
|
||||
|
||||
static {
|
||||
StringBuilder sb = new StringBuilder(8192);
|
||||
for (int i = 0; i < 512; i++) {
|
||||
sb.append("0123456789ABCDEF");
|
||||
}
|
||||
TEXT_8K = sb.toString();
|
||||
BYTES_8K = TEXT_8K.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
@Test
|
||||
public void testPathParmsRootNone() throws Exception {
|
||||
pathParamTest("/", "none");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooNone() throws Exception {
|
||||
pathParamTest("/foo", "none");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsRootSessionOnly() throws Exception {
|
||||
pathParamTest("/;jsessionid=1234", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionOnly() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionDummy() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234;dummy", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionDummyValue() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234;dummy=5678", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionValue() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234;=5678", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParmsFooSessionBar() throws Exception {
|
||||
pathParamTest("/foo;jsessionid=1234/bar", "1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParamsRedirect() throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// Must have a real docBase. Don't use java.io.tmpdir as it may not be
|
||||
// writable.
|
||||
File docBase = new File(getTemporaryDirectory(), "testCoyoteAdapter");
|
||||
addDeleteOnTearDown(docBase);
|
||||
if (!docBase.mkdirs() && !docBase.isDirectory()) {
|
||||
Assert.fail("Failed to create: [" + docBase.toString() + "]");
|
||||
}
|
||||
|
||||
// Create the folder that will trigger the redirect
|
||||
File foo = new File(docBase, "foo");
|
||||
addDeleteOnTearDown(foo);
|
||||
if (!foo.mkdirs() && !foo.isDirectory()) {
|
||||
Assert.fail("Unable to create foo directory in docBase");
|
||||
}
|
||||
|
||||
Context ctx = tomcat.addContext("", docBase.getAbsolutePath());
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new PathParamServlet());
|
||||
ctx.addServletMappingDecoded("/", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
testPath("/", "none");
|
||||
testPath("/;jsessionid=1234", "1234");
|
||||
testPath("/foo;jsessionid=1234", "1234");
|
||||
testPath("/foo;jsessionid=1234;dummy", "1234");
|
||||
testPath("/foo;jsessionid=1234;dummy=5678", "1234");
|
||||
testPath("/foo;jsessionid=1234;=5678", "1234");
|
||||
testPath("/foo;jsessionid=1234/bar", "1234");
|
||||
}
|
||||
|
||||
private void pathParamTest(String path, String expected) throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new PathParamServlet());
|
||||
ctx.addServletMappingDecoded("/", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() + path);
|
||||
Assert.assertEquals(expected, res.toString());
|
||||
}
|
||||
|
||||
private void testPath(String path, String expected) throws Exception {
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() + path);
|
||||
Assert.assertEquals(expected, res.toString());
|
||||
}
|
||||
|
||||
private static class PathParamServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.setContentType("text/plain");
|
||||
PrintWriter pw = resp.getWriter();
|
||||
String sessionId = req.getRequestedSessionId();
|
||||
if (sessionId == null) {
|
||||
sessionId = "none";
|
||||
}
|
||||
pw.write(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParamExtRootNoParam() throws Exception {
|
||||
pathParamExtensionTest("/testapp/blah.txt", "none");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParamExtLevel1NoParam() throws Exception {
|
||||
pathParamExtensionTest("/testapp/blah/blah.txt", "none");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathParamExtLevel1WithParam() throws Exception {
|
||||
pathParamExtensionTest("/testapp/blah;x=y/blah.txt", "none");
|
||||
}
|
||||
|
||||
private void pathParamExtensionTest(String path, String expected)
|
||||
throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("/testapp", null);
|
||||
|
||||
Tomcat.addServlet(ctx, "servlet", new PathParamServlet());
|
||||
ctx.addServletMappingDecoded("*.txt", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk res = getUrl("http://localhost:" + getPort() + path);
|
||||
Assert.assertEquals(expected, res.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602a() throws Exception {
|
||||
// No UTF-8
|
||||
doTestUriDecoding("/foo", "UTF-8", "/foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602b() throws Exception {
|
||||
// Valid UTF-8
|
||||
doTestUriDecoding("/foo%c4%87", "UTF-8", "/foo\u0107");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602c() throws Exception {
|
||||
// Partial UTF-8
|
||||
doTestUriDecoding("/foo%c4", "UTF-8", "/foo\uFFFD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602d() throws Exception {
|
||||
// Invalid UTF-8
|
||||
doTestUriDecoding("/foo%ff", "UTF-8", "/foo\uFFFD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug54602e() throws Exception {
|
||||
// Invalid UTF-8
|
||||
doTestUriDecoding("/foo%ed%a0%80", "UTF-8", "/foo\uFFFD\uFFFD\uFFFD");
|
||||
}
|
||||
|
||||
private void doTestUriDecoding(String path, String encoding,
|
||||
String expectedPathInfo) throws Exception{
|
||||
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
tomcat.getConnector().setURIEncoding(encoding);
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
PathInfoServlet servlet = new PathInfoServlet();
|
||||
Tomcat.addServlet(ctx, "servlet", servlet);
|
||||
ctx.addServletMappingDecoded("/*", "servlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
int rc = getUrl("http://localhost:" + getPort() + path,
|
||||
new ByteChunk(), null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
|
||||
Assert.assertEquals(expectedPathInfo, servlet.getPathInfo());
|
||||
}
|
||||
|
||||
private static class PathInfoServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private volatile String pathInfo = null;
|
||||
|
||||
public String getPathInfo() {
|
||||
return pathInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
// Not thread safe. Concurrent requests to this servlet will
|
||||
// over-write all the results but the last processed.
|
||||
pathInfo = req.getPathInfo();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug54928() throws Exception {
|
||||
// Setup Tomcat instance
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// No file system docBase required
|
||||
Context ctx = tomcat.addContext("", null);
|
||||
|
||||
AsyncServlet servlet = new AsyncServlet();
|
||||
Wrapper w = Tomcat.addServlet(ctx, "async", servlet);
|
||||
w.setAsyncSupported(true);
|
||||
ctx.addServletMappingDecoded("/async", "async");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
SimpleHttpClient client = new SimpleHttpClient() {
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
String request = "GET /async HTTP/1.1" + SimpleHttpClient.CRLF +
|
||||
"Host: a" + SimpleHttpClient.CRLF + SimpleHttpClient.CRLF;
|
||||
|
||||
client.setPort(getPort());
|
||||
client.setRequest(new String[] {request});
|
||||
|
||||
client.connect();
|
||||
client.sendRequest();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
String line = client.readLine();
|
||||
if (line != null && line.length() > 20) {
|
||||
log.info(line.subSequence(0, 20) + "...");
|
||||
}
|
||||
}
|
||||
|
||||
client.disconnect();
|
||||
|
||||
// Wait for server thread to stop
|
||||
Thread t = servlet.getThread();
|
||||
long startTime = System.nanoTime();
|
||||
t.join(5000);
|
||||
long endTime = System.nanoTime();
|
||||
log.info("Waited for servlet thread to stop for "
|
||||
+ (endTime - startTime) / 1000000 + " ms");
|
||||
|
||||
Assert.assertTrue(servlet.isCompleted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalize01() {
|
||||
doTestNormalize("/foo/../bar", "/bar");
|
||||
}
|
||||
|
||||
private void doTestNormalize(String input, String expected) {
|
||||
MessageBytes mb = MessageBytes.newInstance();
|
||||
byte[] b = input.getBytes(StandardCharsets.UTF_8);
|
||||
mb.setBytes(b, 0, b.length);
|
||||
|
||||
boolean result = CoyoteAdapter.normalize(mb);
|
||||
mb.toString();
|
||||
|
||||
if (expected == null) {
|
||||
Assert.assertFalse(result);
|
||||
} else {
|
||||
Assert.assertTrue(result);
|
||||
Assert.assertEquals(expected, mb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class AsyncServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// This is a hack that won't work generally as servlets are expected to
|
||||
// handle more than one request.
|
||||
private Thread t;
|
||||
private volatile boolean completed = false;
|
||||
|
||||
public Thread getThread() {
|
||||
return t;
|
||||
}
|
||||
|
||||
public boolean isCompleted() {
|
||||
return completed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
|
||||
final OutputStream os = resp.getOutputStream();
|
||||
|
||||
final AsyncContext asyncCtxt = req.startAsync();
|
||||
asyncCtxt.setTimeout(3000);
|
||||
|
||||
t = new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
try {
|
||||
// Some tests depend on this write failing (e.g.
|
||||
// because the client has gone away). In some cases
|
||||
// there may be a large (ish) buffer to fill before
|
||||
// the write fails.
|
||||
for (int j = 0 ; j < 8; j++) {
|
||||
os.write(BYTES_8K);
|
||||
}
|
||||
os.flush();
|
||||
Thread.sleep(1000);
|
||||
} catch (Exception e) {
|
||||
log.info("Exception caught " + e);
|
||||
try {
|
||||
// Note if request times out before this
|
||||
// exception is thrown and the complete call
|
||||
// below is made, the complete call below will
|
||||
// fail since the timeout will have completed
|
||||
// the request.
|
||||
asyncCtxt.complete();
|
||||
break;
|
||||
} finally {
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
t.setName("testBug54928");
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.catalina.connector;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
|
||||
import static org.apache.catalina.startup.SimpleHttpClient.CRLF;
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.servlets.DefaultServlet;
|
||||
import org.apache.catalina.startup.SimpleHttpClient;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.unittest.TesterData;
|
||||
|
||||
/*
|
||||
* Various requests, usually originating from fuzzing, that have triggered an
|
||||
* incorrect response from Tomcat - usually a 500 response rather than a 400
|
||||
* response.
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class TestCoyoteAdapterRequestFuzzing extends TomcatBaseTest {
|
||||
|
||||
private static final String VALUE_16K = TesterData.string('x', 16 * 1024);
|
||||
// Default max header count is 100
|
||||
private static final String HEADER_150 = TesterData.string("X-Tomcat-Test: a" + CRLF, 150);
|
||||
// Default max header count is 200 (need to keep under maxHeaderCount as well)
|
||||
private static final String COOKIE_250 = TesterData.string("Cookie: a=b;c=d;e=f;g=h" + CRLF, 75);
|
||||
|
||||
@Parameterized.Parameters(name = "{index}: requestline[{0}], expected[{2}]")
|
||||
public static Collection<Object[]> parameters() {
|
||||
List<Object[]> parameterSets = new ArrayList<>();
|
||||
|
||||
parameterSets.add(new Object[] { "GET /00 HTTP/1.1",
|
||||
"Host: lÿ#" + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET *; HTTP/1.1",
|
||||
"Host: localhost" + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /02 HTTP/1.1",
|
||||
"Host: localhost" + CRLF +
|
||||
"Content-Length: \u00A0" + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /03 HTTP/1.1",
|
||||
"Content-Length: 1" + CRLF +
|
||||
"Content-Length: 1" + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /04 HTTP/1.1",
|
||||
"Transfer-Encoding: " + VALUE_16K + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /05 HTTP/1.1",
|
||||
"Expect: " + VALUE_16K + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /06 HTTP/1.1",
|
||||
"Connection: " + VALUE_16K + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /07 HTTP/1.1",
|
||||
"User-Agent: " + VALUE_16K + CRLF,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /08 HTTP/1.1",
|
||||
HEADER_150,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET http://host/09 HTTP/1.0",
|
||||
HEADER_150,
|
||||
"400" } );
|
||||
parameterSets.add(new Object[] { "GET /10 HTTP/1.1",
|
||||
"Host: localhost" + CRLF +
|
||||
COOKIE_250,
|
||||
"400" } );
|
||||
|
||||
return parameterSets;
|
||||
}
|
||||
|
||||
@Parameter(0)
|
||||
public String requestLine;
|
||||
|
||||
@Parameter(1)
|
||||
public String headers;
|
||||
|
||||
@Parameter(2)
|
||||
public String expected;
|
||||
|
||||
|
||||
@Test
|
||||
public void doTest() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
tomcat.getConnector().setAttribute("restrictedUserAgents", "value-not-important");
|
||||
|
||||
File appDir = new File("test/webapp");
|
||||
Context ctxt = tomcat.addContext("", appDir.getAbsolutePath());
|
||||
Tomcat.addServlet(ctxt, "default", DefaultServlet.class.getName());
|
||||
ctxt.addServletMappingDecoded("/", "default");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
Client client = new Client(tomcat.getConnector().getLocalPort());
|
||||
client.setRequest(new String[] {requestLine + CRLF, headers + CRLF});
|
||||
|
||||
client.connect();
|
||||
client.processRequest();
|
||||
|
||||
// Expected response
|
||||
String line = client.getResponseLine();
|
||||
Assert.assertTrue(line + CRLF + client.getResponseBody(), line.startsWith("HTTP/1.1 " + expected + " "));
|
||||
}
|
||||
|
||||
|
||||
private static final class Client extends SimpleHttpClient {
|
||||
|
||||
public Client(int port) {
|
||||
setPort(port);
|
||||
setRequestPause(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream createOutputStream(Socket socket) throws IOException {
|
||||
// Override the default implementation so we can create a large
|
||||
// enough buffer to hold the entire request.
|
||||
// The default implementation uses the 8k buffer in the
|
||||
// StreamEncoder. Since some requests are larger than this, those
|
||||
// requests will be sent in several parts. If the first part is
|
||||
// sufficient for Tomcat to determine the request is invalid, Tomcat
|
||||
// will close the connection, causing the write of the remaining
|
||||
// parts to fail which in turn causes the test to fail.
|
||||
return new BufferedOutputStream(super.createOutputStream(socket), 32 * 1024);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
// Response body varies. It is the response code that is of interest
|
||||
// in these tests.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.catalina.connector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestCoyoteInputStream extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testReadWithByteBuffer() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "testServlet", new TestServlet());
|
||||
root.addServletMappingDecoded("/", "testServlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
String requestBody = "HelloWorld";
|
||||
int rc = postUrl(requestBody.getBytes(StandardCharsets.UTF_8),
|
||||
"http://localhost:" + getPort() + "/", bc, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertTrue(requestBody.equals(bc.toString()));
|
||||
}
|
||||
|
||||
private static final class TestServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
CoyoteInputStream is = (CoyoteInputStream) req.getInputStream();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(256);
|
||||
is.read(buffer);
|
||||
CoyoteOutputStream os = (CoyoteOutputStream) resp.getOutputStream();
|
||||
os.write(buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
293
test/org/apache/catalina/connector/TestCoyoteOutputStream.java
Normal file
293
test/org/apache/catalina/connector/TestCoyoteOutputStream.java
Normal file
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* 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.catalina.connector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel.MapMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.WriteListener;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestCoyoteOutputStream extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteNoneBlockingWriteNoneContainerThread() throws Exception {
|
||||
doNonBlockingTest(0, 0, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteOnceBlockingWriteNoneContainerThread() throws Exception {
|
||||
doNonBlockingTest(1, 0, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteTwiceBlockingWriteNoneContainerThread() throws Exception {
|
||||
doNonBlockingTest(2, 0, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteNoneBlockingWriteOnceContainerThread() throws Exception {
|
||||
doNonBlockingTest(0, 1, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteOnceBlockingWriteOnceContainerThread() throws Exception {
|
||||
doNonBlockingTest(1, 1, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteTwiceBlockingWriteOnceContainerThread() throws Exception {
|
||||
doNonBlockingTest(2, 1, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteNoneBlockingWriteNoneNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(0, 0, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteOnceBlockingWriteNoneNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(1, 0, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteTwiceBlockingWriteNoneNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(2, 0, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteNoneBlockingWriteOnceNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(0, 1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteOnceBlockingWriteOnceNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(1, 1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockingWriteTwiceBlockingWriteOnceNonContainerThread() throws Exception {
|
||||
doNonBlockingTest(2, 1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteWithByteBuffer() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "testServlet", new TestServlet());
|
||||
root.addServletMappingDecoded("/", "testServlet");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/", bc, null, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
File file = new File("test/org/apache/catalina/connector/test_content.txt");
|
||||
try (RandomAccessFile raf = new RandomAccessFile(file, "r");) {
|
||||
ByteChunk expected = new ByteChunk();
|
||||
expected.append(raf.getChannel().map(MapMode.READ_ONLY, 0, file.length()));
|
||||
Assert.assertTrue(expected.equals(bc));
|
||||
}
|
||||
}
|
||||
|
||||
private void doNonBlockingTest(int asyncWriteTarget, int syncWriteTarget,
|
||||
boolean useContainerThreadToSetListener) throws Exception {
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Wrapper w = Tomcat.addServlet(root, "nbWrite",
|
||||
new NonBlockingWriteServlet(asyncWriteTarget, useContainerThreadToSetListener));
|
||||
w.setAsyncSupported(true);
|
||||
root.addServletMappingDecoded("/nbWrite", "nbWrite");
|
||||
Tomcat.addServlet(root, "write",
|
||||
new BlockingWriteServlet(asyncWriteTarget, syncWriteTarget));
|
||||
w.setAsyncSupported(true);
|
||||
root.addServletMappingDecoded("/write", "write");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
// Extend timeout to 5 mins for debugging
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/nbWrite", bc,
|
||||
300000, null, null);
|
||||
|
||||
int totalCount = asyncWriteTarget + syncWriteTarget;
|
||||
StringBuilder sb = new StringBuilder(totalCount * 16);
|
||||
for (int i = 0; i < totalCount; i++) {
|
||||
sb.append("OK - " + i + System.lineSeparator());
|
||||
}
|
||||
String expected = null;
|
||||
if (sb.length() > 0) {
|
||||
expected = sb.toString();
|
||||
}
|
||||
Assert.assertEquals(200, rc);
|
||||
Assert.assertEquals(expected, bc.toString());
|
||||
}
|
||||
|
||||
private static final class NonBlockingWriteServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final int asyncWriteTarget;
|
||||
private final AtomicInteger asyncWriteCount = new AtomicInteger(0);
|
||||
private final boolean useContainerThreadToSetListener;
|
||||
|
||||
public NonBlockingWriteServlet(int asyncWriteTarget,
|
||||
boolean useContainerThreadToSetListener) {
|
||||
this.asyncWriteTarget = asyncWriteTarget;
|
||||
this.useContainerThreadToSetListener = useContainerThreadToSetListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
ServletOutputStream sos = resp.getOutputStream();
|
||||
|
||||
|
||||
AsyncContext asyncCtxt = req.startAsync();
|
||||
asyncCtxt.setTimeout(5);
|
||||
Runnable task = new AsyncTask(asyncCtxt, sos);
|
||||
if (useContainerThreadToSetListener) {
|
||||
asyncCtxt.start(task);
|
||||
} else {
|
||||
Thread t = new Thread(task);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void doAsyncWrite(AsyncContext asyncCtxt,
|
||||
ServletOutputStream sos) throws IOException {
|
||||
while (sos.isReady()) {
|
||||
int next = asyncWriteCount.getAndIncrement();
|
||||
if (next < asyncWriteTarget) {
|
||||
sos.write(
|
||||
("OK - " + next + System.lineSeparator()).getBytes(
|
||||
StandardCharsets.UTF_8));
|
||||
sos.flush();
|
||||
} else {
|
||||
asyncCtxt.dispatch("/write");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AsyncTask implements Runnable {
|
||||
|
||||
private final AsyncContext asyncCtxt;
|
||||
private final ServletOutputStream sos;
|
||||
|
||||
public AsyncTask(AsyncContext asyncCtxt, ServletOutputStream sos) {
|
||||
this.asyncCtxt = asyncCtxt;
|
||||
this.sos = sos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
sos.setWriteListener(new MyWriteListener(asyncCtxt, sos));
|
||||
}
|
||||
}
|
||||
|
||||
private final class MyWriteListener implements WriteListener {
|
||||
|
||||
private final AsyncContext asyncCtxt;
|
||||
private final ServletOutputStream sos;
|
||||
|
||||
public MyWriteListener(AsyncContext asyncCtxt,
|
||||
ServletOutputStream sos) {
|
||||
this.asyncCtxt = asyncCtxt;
|
||||
this.sos = sos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWritePossible() throws IOException {
|
||||
doAsyncWrite(asyncCtxt, sos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
// Not expected.
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BlockingWriteServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final int start;
|
||||
private final int len;
|
||||
|
||||
public BlockingWriteServlet(int start, int len) {
|
||||
this.start = start;
|
||||
this.len = len;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
ServletOutputStream sos = resp.getOutputStream();
|
||||
|
||||
for (int i = start; i < start + len; i++) {
|
||||
sos.write(("OK - " + i + System.lineSeparator()).getBytes(
|
||||
StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
CoyoteOutputStream os = (CoyoteOutputStream) resp.getOutputStream();
|
||||
File file = new File("test/org/apache/catalina/connector/test_content.txt");
|
||||
try (RandomAccessFile raf = new RandomAccessFile(file, "r");) {
|
||||
os.write(raf.getChannel().map(MapMode.READ_ONLY, 0, file.length()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
160
test/org/apache/catalina/connector/TestInputBuffer.java
Normal file
160
test/org/apache/catalina/connector/TestInputBuffer.java
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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.catalina.connector;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.MalformedInputException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
import org.apache.tomcat.util.buf.TestUtf8;
|
||||
import org.apache.tomcat.util.buf.TestUtf8.Utf8TestCase;
|
||||
|
||||
public class TestInputBuffer extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testUtf8Body() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "Echo", new Utf8Echo());
|
||||
root.addServletMappingDecoded("/test", "Echo");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
for (Utf8TestCase testCase : TestUtf8.TEST_CASES) {
|
||||
String expected = null;
|
||||
if (testCase.invalidIndex == -1) {
|
||||
expected = testCase.outputReplaced;
|
||||
}
|
||||
doUtf8BodyTest(testCase.description, testCase.input, expected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug60400() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "Bug60400Servlet", new Bug60400Servlet());
|
||||
root.addServletMappingDecoded("/", "Bug60400Servlet");
|
||||
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("socket.appReadBufSize", "9000"));
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
byte[] requestBody = new byte[9500];
|
||||
Arrays.fill(requestBody, (byte) 1);
|
||||
int rc = postUrl(requestBody, "http://localhost:" + getPort() + "/", bc, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(requestBody.length, bc.getLength());
|
||||
}
|
||||
|
||||
|
||||
private void doUtf8BodyTest(String description, int[] input,
|
||||
String expected) throws Exception {
|
||||
|
||||
byte[] bytes = new byte[input.length];
|
||||
for (int i = 0; i < input.length; i++) {
|
||||
bytes[i] = (byte) input[i];
|
||||
}
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
int rc = postUrl(bytes, "http://localhost:" + getPort() + "/test", bc,
|
||||
null);
|
||||
|
||||
if (expected == null) {
|
||||
Assert.assertEquals(description, HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(description, "FAILED", bc.toString());
|
||||
} else if (expected.length() == 0) {
|
||||
Assert.assertNull(description, bc.toString());
|
||||
} else {
|
||||
bc.setCharset(StandardCharsets.UTF_8);
|
||||
Assert.assertEquals(description, expected, bc.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class Utf8Echo extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
// Should use POST
|
||||
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
Reader r = req.getReader();
|
||||
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/plain");
|
||||
Writer w = resp.getWriter();
|
||||
|
||||
try {
|
||||
// Copy one character at a time
|
||||
int c = r.read();
|
||||
while (c != -1) {
|
||||
w.write(c);
|
||||
c = r.read();
|
||||
}
|
||||
w.close();
|
||||
} catch (MalformedInputException mie) {
|
||||
resp.resetBuffer();
|
||||
w.write("FAILED");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class Bug60400Servlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
try (BufferedReader reader = req.getReader()) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
builder.append(line);
|
||||
}
|
||||
}
|
||||
resp.getWriter().print(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
141
test/org/apache/catalina/connector/TestKeepAliveCount.java
Normal file
141
test/org/apache/catalina/connector/TestKeepAliveCount.java
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.catalina.connector;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.SimpleHttpClient;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
|
||||
public class TestKeepAliveCount extends TomcatBaseTest {
|
||||
|
||||
@Test
|
||||
public void testHttp10() throws Exception {
|
||||
TestKeepAliveClient client = new TestKeepAliveClient();
|
||||
client.doHttp10Request();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttp11() throws Exception {
|
||||
TestKeepAliveClient client = new TestKeepAliveClient();
|
||||
client.doHttp11Request();
|
||||
}
|
||||
|
||||
|
||||
private class TestKeepAliveClient extends SimpleHttpClient {
|
||||
|
||||
|
||||
private boolean init;
|
||||
|
||||
private synchronized void init() {
|
||||
if (init) return;
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "Simple", new SimpleServlet());
|
||||
root.addServletMappingDecoded("/test", "Simple");
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "5"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("connectionTimeout", "20000"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("keepAliveTimeout", "50000"));
|
||||
init = true;
|
||||
}
|
||||
|
||||
private void doHttp10Request() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
init();
|
||||
tomcat.start();
|
||||
setPort(tomcat.getConnector().getLocalPort());
|
||||
|
||||
// Open connection
|
||||
connect();
|
||||
|
||||
// Send request in two parts
|
||||
String[] request = new String[1];
|
||||
request[0] =
|
||||
"GET /test HTTP/1.0" + CRLF + CRLF;
|
||||
setRequest(request);
|
||||
processRequest(false); // blocks until response has been read
|
||||
boolean passed = (this.readLine()==null);
|
||||
// Close the connection
|
||||
disconnect();
|
||||
reset();
|
||||
tomcat.stop();
|
||||
Assert.assertTrue(passed);
|
||||
}
|
||||
|
||||
private void doHttp11Request() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
init();
|
||||
tomcat.start();
|
||||
setPort(tomcat.getConnector().getLocalPort());
|
||||
|
||||
// Open connection
|
||||
connect();
|
||||
|
||||
// Send request in two parts
|
||||
String[] request = new String[1];
|
||||
request[0] =
|
||||
"GET /test HTTP/1.1" + CRLF +
|
||||
"Host: localhost" + CRLF +
|
||||
"Connection: Keep-Alive" + CRLF+
|
||||
"Keep-Alive: 300"+ CRLF+ CRLF;
|
||||
|
||||
setRequest(request);
|
||||
|
||||
for (int i=0; i<5; i++) {
|
||||
processRequest(false); // blocks until response has been read
|
||||
Assert.assertTrue(getResponseLine()!=null && getResponseLine().startsWith("HTTP/1.1 200 "));
|
||||
}
|
||||
boolean passed = (this.readLine()==null);
|
||||
// Close the connection
|
||||
disconnect();
|
||||
reset();
|
||||
tomcat.stop();
|
||||
Assert.assertTrue(passed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
resp.setContentLength(0);
|
||||
resp.flushBuffer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
162
test/org/apache/catalina/connector/TestMaxConnections.java
Normal file
162
test/org/apache/catalina/connector/TestMaxConnections.java
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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.catalina.connector;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.core.StandardContext;
|
||||
import org.apache.catalina.startup.SimpleHttpClient;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
|
||||
public class TestMaxConnections extends TomcatBaseTest {
|
||||
private static final int MAX_CONNECTIONS = 3;
|
||||
public static final int soTimeout = 5000;
|
||||
public static final int connectTimeout = 1000;
|
||||
|
||||
@Test
|
||||
public void testConnector() throws Exception {
|
||||
init();
|
||||
Assume.assumeFalse("This feature is not available for NIO2 (BZ58103)",
|
||||
getTomcatInstance().getConnector().getProtocolHandlerClassName().contains("Nio2"));
|
||||
ConnectThread[] t = new ConnectThread[10];
|
||||
for (int i=0; i<t.length; i++) {
|
||||
t[i] = new ConnectThread();
|
||||
t[i].setName("ConnectThread["+i+"]");
|
||||
}
|
||||
for (int i=0; i<t.length; i++) {
|
||||
t[i].start();
|
||||
Thread.sleep(50);
|
||||
}
|
||||
for (int i=0; i<t.length; i++) {
|
||||
t[i].join();
|
||||
}
|
||||
|
||||
Assert.assertEquals(MAX_CONNECTIONS, SimpleServlet.getMaxConnections());
|
||||
}
|
||||
|
||||
private class ConnectThread extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
TestClient client = new TestClient();
|
||||
client.doHttp10Request();
|
||||
} catch (Exception x) {
|
||||
// NO-OP. Some connections are expected to fail.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private synchronized void init() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
StandardContext root = (StandardContext) tomcat.addContext("", SimpleHttpClient.TEMP_DIR);
|
||||
root.setUnloadDelay(soTimeout);
|
||||
Tomcat.addServlet(root, "Simple", new SimpleServlet());
|
||||
root.addServletMappingDecoded("/test", "Simple");
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("maxThreads", "10"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("connectionTimeout", "20000"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("keepAliveTimeout", "50000"));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("maxConnections", Integer.toString(MAX_CONNECTIONS)));
|
||||
Assert.assertTrue(tomcat.getConnector().setProperty("acceptCount", "1"));
|
||||
tomcat.start();
|
||||
}
|
||||
|
||||
private class TestClient extends SimpleHttpClient {
|
||||
|
||||
private void doHttp10Request() throws Exception {
|
||||
setPort(getPort());
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
// Open connection
|
||||
connect(connectTimeout,soTimeout);
|
||||
|
||||
// Send request in two parts
|
||||
String[] request = new String[1];
|
||||
request[0] =
|
||||
"GET /test HTTP/1.0" + CRLF + CRLF;
|
||||
setRequest(request);
|
||||
boolean passed = false;
|
||||
processRequest(false); // blocks until response has been read
|
||||
long stop = System.currentTimeMillis();
|
||||
log.info(Thread.currentThread().getName()+" Request complete:"+(stop-start)+" ms.");
|
||||
passed = (this.readLine()==null);
|
||||
// Close the connection
|
||||
disconnect();
|
||||
reset();
|
||||
Assert.assertTrue(passed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResponseBodyOK() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static int currentConnections = 0;
|
||||
private static int maxConnections = 0;
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
increment();
|
||||
|
||||
System.out.println("Processing thread: " + Thread.currentThread().getName());
|
||||
try {
|
||||
Thread.sleep(TestMaxConnections.soTimeout*4/5);
|
||||
} catch (InterruptedException x) {
|
||||
|
||||
}
|
||||
resp.setContentLength(0);
|
||||
resp.flushBuffer();
|
||||
|
||||
decrement();
|
||||
}
|
||||
|
||||
private static synchronized void increment() {
|
||||
currentConnections++;
|
||||
if (currentConnections > maxConnections) {
|
||||
maxConnections = currentConnections;
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized void decrement() {
|
||||
currentConnections--;
|
||||
}
|
||||
|
||||
|
||||
public static synchronized int getMaxConnections() {
|
||||
return maxConnections;
|
||||
}
|
||||
}
|
||||
}
|
||||
214
test/org/apache/catalina/connector/TestOutputBuffer.java
Normal file
214
test/org/apache/catalina/connector/TestOutputBuffer.java
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* 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.catalina.connector;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestOutputBuffer extends TomcatBaseTest{
|
||||
|
||||
/*
|
||||
* Expect that the buffered results are slightly slower since Tomcat now has
|
||||
* an internal buffer so an extra one just adds overhead.
|
||||
*
|
||||
* @see "https://bz.apache.org/bugzilla/show_bug.cgi?id=52328"
|
||||
*/
|
||||
@Test
|
||||
public void testWriteSpeed() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
|
||||
for (int i = 1; i <= WritingServlet.EXPECTED_CONTENT_LENGTH; i*=10) {
|
||||
WritingServlet servlet = new WritingServlet(i);
|
||||
Tomcat.addServlet(root, "servlet" + i, servlet);
|
||||
root.addServletMappingDecoded("/servlet" + i, "servlet" + i);
|
||||
}
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
|
||||
for (int i = 1; i <= WritingServlet.EXPECTED_CONTENT_LENGTH; i*=10) {
|
||||
int rc = getUrl("http://localhost:" + getPort() +
|
||||
"/servlet" + i, bc, null, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(
|
||||
WritingServlet.EXPECTED_CONTENT_LENGTH, bc.getLength());
|
||||
|
||||
bc.recycle();
|
||||
|
||||
rc = getUrl("http://localhost:" + getPort() +
|
||||
"/servlet" + i + "?useBuffer=y", bc, null, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(
|
||||
WritingServlet.EXPECTED_CONTENT_LENGTH, bc.getLength());
|
||||
|
||||
bc.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBug52577() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
|
||||
Bug52577Servlet bug52577 = new Bug52577Servlet();
|
||||
Tomcat.addServlet(root, "bug52577", bug52577);
|
||||
root.addServletMappingDecoded("/", "bug52577");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/", bc, null, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals("OK", bc.toString());
|
||||
}
|
||||
|
||||
private static class WritingServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected static final int EXPECTED_CONTENT_LENGTH = 100000;
|
||||
|
||||
private final String writeString;
|
||||
private final int writeCount;
|
||||
|
||||
public WritingServlet(int writeLength) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < writeLength; i++) {
|
||||
sb.append('x');
|
||||
}
|
||||
writeString = sb.toString();
|
||||
writeCount = EXPECTED_CONTENT_LENGTH / writeLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("ISO-8859-1");
|
||||
|
||||
Writer w = resp.getWriter();
|
||||
|
||||
// Wrap with a buffer if necessary
|
||||
String useBufferStr = req.getParameter("useBuffer");
|
||||
if (useBufferStr != null) {
|
||||
w = new BufferedWriter(w);
|
||||
}
|
||||
|
||||
long start = System.nanoTime();
|
||||
for (int i = 0; i < writeCount; i++) {
|
||||
w.write(writeString);
|
||||
}
|
||||
if (useBufferStr != null) {
|
||||
w.flush();
|
||||
}
|
||||
long lastRunNano = System.nanoTime() - start;
|
||||
|
||||
System.out.println("Write length: " + writeString.length() +
|
||||
", Buffered: " + (useBufferStr == null ? "n" : "y") +
|
||||
", Time: " + lastRunNano + "ns");
|
||||
}
|
||||
}
|
||||
|
||||
private static class Bug52577Servlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
Writer w = resp.getWriter();
|
||||
w.write("OK");
|
||||
resp.resetBuffer();
|
||||
w.write("OK");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUtf8SurrogateBody() throws Exception {
|
||||
// Create test data. This is carefully constructed to trigger the edge
|
||||
// case. Small variations may cause the test to miss the edge case.
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("a");
|
||||
|
||||
for (int i = 0x10000; i < 0x11000; i++) {
|
||||
char[] chars = Character.toChars(i);
|
||||
sb.append(chars);
|
||||
}
|
||||
String data = sb.toString();
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
Tomcat.addServlet(root, "Test", new Utf8WriteChars(data));
|
||||
root.addServletMappingDecoded("/test", "Test");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
getUrl("http://localhost:" + getPort() + "/test", bc, null);
|
||||
|
||||
bc.setCharset(StandardCharsets.UTF_8);
|
||||
Assert.assertEquals(data, bc.toString());
|
||||
}
|
||||
|
||||
|
||||
private static class Utf8WriteChars extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final char[] chars;
|
||||
|
||||
public Utf8WriteChars(String data) {
|
||||
chars = data.toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/plain");
|
||||
Writer w = resp.getWriter();
|
||||
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
w.write(chars[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
969
test/org/apache/catalina/connector/TestRequest.java
Normal file
969
test/org/apache/catalina/connector/TestRequest.java
Normal file
File diff suppressed because it is too large
Load Diff
662
test/org/apache/catalina/connector/TestResponse.java
Normal file
662
test/org/apache/catalina/connector/TestResponse.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.catalina.connector;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.startup.LoggingBaseTest;
|
||||
import org.apache.tomcat.unittest.TesterRequest;
|
||||
|
||||
public class TestResponsePerformance extends LoggingBaseTest {
|
||||
|
||||
private final int ITERATIONS = 100000;
|
||||
|
||||
@Test
|
||||
public void testToAbsolutePerformance() throws Exception {
|
||||
Request req = new TesterRequest();
|
||||
Response resp = new Response();
|
||||
resp.setRequest(req);
|
||||
|
||||
// Warm up
|
||||
doHomebrew(resp);
|
||||
doUri();
|
||||
|
||||
// Note: Java 9 on my OSX laptop consistently shows doUri() is faster
|
||||
// than doHomebrew(). Worth a closer look for Tomcat 10 on the
|
||||
// assumption it will require java 9
|
||||
|
||||
// To allow for timing differences between runs, a "best of n" approach
|
||||
// is taken for this test
|
||||
final int bestOf = 5;
|
||||
final int winTarget = (bestOf + 1) / 2;
|
||||
int homebrewWin = 0;
|
||||
int count = 0;
|
||||
|
||||
while (count < bestOf && homebrewWin < winTarget) {
|
||||
long homebrew = doHomebrew(resp);
|
||||
long uri = doUri();
|
||||
log.info("Current 'home-brew': " + homebrew + "ms, Using URI: " + uri + "ms");
|
||||
if (homebrew < uri) {
|
||||
homebrewWin++;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
Assert.assertTrue(homebrewWin == winTarget);
|
||||
}
|
||||
|
||||
|
||||
private long doHomebrew(Response resp) {
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
resp.toAbsolute("bar.html");
|
||||
}
|
||||
return System.currentTimeMillis() - start;
|
||||
}
|
||||
|
||||
|
||||
private long doUri() {
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
URI base = URI.create(
|
||||
"http://localhost:8080/level1/level2/foo.html");
|
||||
base.resolve(URI.create("bar.html")).toASCIIString();
|
||||
}
|
||||
return System.currentTimeMillis() - start;
|
||||
}
|
||||
}
|
||||
251
test/org/apache/catalina/connector/TestSendFile.java
Normal file
251
test/org/apache/catalina/connector/TestSendFile.java
Normal file
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* 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.catalina.connector;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Globals;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.TomcatBaseTest;
|
||||
import org.apache.tomcat.util.buf.ByteChunk;
|
||||
|
||||
public class TestSendFile extends TomcatBaseTest {
|
||||
|
||||
public static final int ITERATIONS = 10;
|
||||
public static final int EXPECTED_CONTENT_LENGTH = 100000;
|
||||
|
||||
@Test
|
||||
public void testSendFile() throws Exception {
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context root = tomcat.addContext("", TEMP_DIR);
|
||||
|
||||
File[] files = new File[ITERATIONS];
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
files[i] = generateFile(TEMP_DIR, "-" + i, EXPECTED_CONTENT_LENGTH * (i + 1));
|
||||
}
|
||||
try {
|
||||
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
WritingServlet servlet = new WritingServlet(files[i]);
|
||||
Tomcat.addServlet(root, "servlet" + i, servlet);
|
||||
root.addServletMappingDecoded("/servlet" + i, "servlet" + i);
|
||||
}
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
Map<String, List<String>> respHeaders = new HashMap<>();
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
long start = System.currentTimeMillis();
|
||||
int rc = getUrl("http://localhost:" + getPort() + "/servlet" + i, bc, null,
|
||||
respHeaders);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
System.out.println("Client received " + bc.getLength() + " bytes in "
|
||||
+ (System.currentTimeMillis() - start) + " ms.");
|
||||
Assert.assertEquals(EXPECTED_CONTENT_LENGTH * (i + 1L), bc.getLength());
|
||||
|
||||
bc.recycle();
|
||||
}
|
||||
} finally {
|
||||
for (File f : files) {
|
||||
Assert.assertTrue("Failed to clean up [" + f + "]", f.delete());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public File generateFile(String dir, String suffix, int size) throws IOException {
|
||||
String name = "testSendFile-" + System.currentTimeMillis() + suffix + ".txt";
|
||||
File f = new File(dir, name);
|
||||
try (FileWriter fw = new FileWriter(f, false); BufferedWriter w = new BufferedWriter(fw);) {
|
||||
int defSize = 8192;
|
||||
while (size > 0) {
|
||||
int bytes = Math.min(size, defSize);
|
||||
char[] b = new char[bytes];
|
||||
Arrays.fill(b, 'X');
|
||||
w.write(b);
|
||||
size = size - bytes;
|
||||
}
|
||||
w.flush();
|
||||
}
|
||||
System.out
|
||||
.println("Created file:" + f.getAbsolutePath() + " with " + f.length() + " bytes.");
|
||||
return f;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class WritingServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final File f;
|
||||
|
||||
public WritingServlet(File f) {
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
resp.setContentType("'application/octet-stream");
|
||||
resp.setCharacterEncoding("ISO-8859-1");
|
||||
resp.setContentLengthLong(f.length());
|
||||
if (Boolean.TRUE.equals(req.getAttribute(Globals.SENDFILE_SUPPORTED_ATTR))) {
|
||||
req.setAttribute(Globals.SENDFILE_FILENAME_ATTR, f.getAbsolutePath());
|
||||
req.setAttribute(Globals.SENDFILE_FILE_START_ATTR, Long.valueOf(0));
|
||||
req.setAttribute(Globals.SENDFILE_FILE_END_ATTR, Long.valueOf(f.length()));
|
||||
} else {
|
||||
byte[] c = new byte[8192];
|
||||
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(f))) {
|
||||
int len = 0;
|
||||
int written = 0;
|
||||
long start = System.currentTimeMillis();
|
||||
do {
|
||||
len = in.read(c);
|
||||
if (len > 0) {
|
||||
resp.getOutputStream().write(c, 0, len);
|
||||
written += len;
|
||||
}
|
||||
} while (len > 0);
|
||||
System.out.println("Server Wrote " + written + " bytes in "
|
||||
+ (System.currentTimeMillis() - start) + " ms.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBug60409() throws Exception {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context ctx = tomcat.addContext("", TEMP_DIR);
|
||||
File file = generateFile(TEMP_DIR, "", EXPECTED_CONTENT_LENGTH);
|
||||
Tomcat.addServlet(ctx, "test", new Bug60409Servlet(file));
|
||||
ctx.addServletMappingDecoded("/", "test");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
ByteChunk bc = new ByteChunk();
|
||||
getUrl("http://localhost:" + getPort() + "/test/?" + Globals.SENDFILE_SUPPORTED_ATTR
|
||||
+ "=true", bc, null);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
List<Throwable> exceptions = new ArrayList<>();
|
||||
new Thread(
|
||||
new RequestExecutor("http://localhost:" + getPort() + "/test/", latch, exceptions))
|
||||
.start();
|
||||
new Thread(
|
||||
new RequestExecutor("http://localhost:" + getPort() + "/test/", latch, exceptions))
|
||||
.start();
|
||||
|
||||
latch.await(3000, TimeUnit.MILLISECONDS);
|
||||
|
||||
if (exceptions.size() > 0) {
|
||||
Assert.fail();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Bug60409Servlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final File file;
|
||||
|
||||
Bug60409Servlet(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
if (Boolean.valueOf(req.getParameter(Globals.SENDFILE_SUPPORTED_ATTR)).booleanValue()) {
|
||||
resp.setContentType("'application/octet-stream");
|
||||
resp.setCharacterEncoding("ISO-8859-1");
|
||||
resp.setContentLengthLong(file.length());
|
||||
req.setAttribute(Globals.SENDFILE_FILENAME_ATTR, file.getAbsolutePath());
|
||||
req.setAttribute(Globals.SENDFILE_FILE_START_ATTR, Long.valueOf(0));
|
||||
req.setAttribute(Globals.SENDFILE_FILE_END_ATTR, Long.valueOf(file.length()));
|
||||
if (!file.delete()) {
|
||||
throw new ServletException("Failed to delete [" + file + "]");
|
||||
}
|
||||
} else {
|
||||
byte[] c = new byte[1024];
|
||||
Random rd = new Random();
|
||||
rd.nextBytes(c);
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
resp.getOutputStream().write(c);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class RequestExecutor implements Runnable {
|
||||
private final String url;
|
||||
private final CountDownLatch latch;
|
||||
private final List<Throwable> exceptions;
|
||||
|
||||
RequestExecutor(String url, CountDownLatch latch, List<Throwable> exceptions) {
|
||||
this.url = url;
|
||||
this.latch = latch;
|
||||
this.exceptions = exceptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ByteChunk result = new ByteChunk();
|
||||
int rc = getUrl(url, result, null);
|
||||
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
|
||||
Assert.assertEquals(1024, result.getLength());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
exceptions.add(e);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
19
test/org/apache/catalina/connector/test_content.txt
Normal file
19
test/org/apache/catalina/connector/test_content.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
# 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.
|
||||
|
||||
# This is a test file for the WebappServiceLoader
|
||||
# It contains comment lines and blank lines
|
||||
|
||||
test content
|
||||
Reference in New Issue
Block a user