Author: markt Date: Thu Jun 2 13:08:38 2016 New Revision: 1746554 URL: http://svn.apache.org/viewvc?rev=1746554&view=rev Log: Add new test case for read timeout during non-blocking IO
Added: tomcat/trunk/test/org/apache/coyote/TestIoTimeouts.java (with props) Added: tomcat/trunk/test/org/apache/coyote/TestIoTimeouts.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/TestIoTimeouts.java?rev=1746554&view=auto ============================================================================== --- tomcat/trunk/test/org/apache/coyote/TestIoTimeouts.java (added) +++ tomcat/trunk/test/org/apache/coyote/TestIoTimeouts.java Thu Jun 2 13:08:38 2016 @@ -0,0 +1,241 @@ +/* + * 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; + +import java.io.IOException; + +import javax.servlet.AsyncContext; +import javax.servlet.ReadListener; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +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.SimpleHttpClient; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; + +public class TestIoTimeouts extends TomcatBaseTest { + + @Test + public void testNonBlockingReadWithNoTimeout() { + // Sends complete request in 3 packets + ChunkedClient client = new ChunkedClient(true); + client.doRequest(); + Assert.assertTrue(client.isResponse200()); + Assert.assertTrue(client.isResponseBodyOK()); + Assert.assertNull(EchoListener.t); + } + + + @Test + public void testNonBlockingReadTimeout() { + // Sends incomplete request (no end chunk) so read times out + ChunkedClient client = new ChunkedClient(false); + client.doRequest(); + Assert.assertFalse(client.isResponse200()); + Assert.assertFalse(client.isResponseBodyOK()); + // Socket will be closed before the error handler runs. Closing the + // socket triggers the client code's return from the doRequest() method + // above so we need to wait at this point for the error handler to be + // triggered. + int count = 0; + // Shouldn't need to wait long but allow plenty of time as the CI + // systems are sometimes slow. + while (count < 100 && EchoListener.t == null) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // Ignore + } + count++; + } + Assert.assertNotNull(EchoListener.t); + } + + + private class ChunkedClient extends SimpleHttpClient { + + private final boolean sendEndChunk; + + + public ChunkedClient(boolean sendEndChunk) { + this.sendEndChunk = sendEndChunk; + } + + + private Exception doRequest() { + + Tomcat tomcat = getTomcatInstance(); + + Context root = tomcat.addContext("", TEMP_DIR); + Wrapper w = Tomcat.addServlet(root, "Test", new NonBlockingEchoServlet()); + w.setAsyncSupported(true); + root.addServletMapping("/test", "Test"); + + try { + tomcat.start(); + setPort(tomcat.getConnector().getLocalPort()); + + // Open connection + connect(); + + int packetCount = 2; + if (sendEndChunk) { + packetCount++; + } + + String[] request = new String[packetCount]; + request[0] = + "POST /test HTTP/1.1" + CRLF + + "Host: localhost:8080" + CRLF + + "Transfer-Encoding: chunked" + CRLF + + "Connection: close" + CRLF + + CRLF; + request[1] = + "b8" + CRLF + + "{" + CRLF + + " \"tenantId\": \"dotCom\", " + CRLF + + " \"locale\": \"en-US\", " + CRLF + + " \"defaultZoneId\": \"25\", " + CRLF + + " \"itemIds\": [\"StaplesUSCAS/en-US/2/<EOF>/<EOF>\"] , " + CRLF + + " \"assetStoreId\": \"5051\", " + CRLF + + " \"zipCode\": \"98109\"" + CRLF + + "}" + CRLF; + if (sendEndChunk) { + request[2] = + "0" + CRLF + + CRLF; + } + + setRequest(request); + processRequest(); // blocks until response has been read + + // Close the connection + disconnect(); + } catch (Exception e) { + return e; + } + return null; + } + + @Override + public boolean isResponseBodyOK() { + if (getResponseBody() == null) { + return false; + } + if (!getResponseBody().contains("98109")) { + return false; + } + return true; + } + + } + + + private static class NonBlockingEchoServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + // Need to be in async mode to use non-blocking I/O + AsyncContext ac = req.startAsync(); + ac.setTimeout(10000); + + ServletInputStream sis = null; + ServletOutputStream sos = null; + + try { + sis = req.getInputStream(); + sos = resp.getOutputStream(); + } catch (IOException ioe) { + throw new ServletException(ioe); + } + + EchoListener listener = new EchoListener(ac, sis, sos); + sis.setReadListener(listener); + sos.setWriteListener(listener); + } + } + + + private static class EchoListener implements ReadListener, WriteListener { + + private static volatile Throwable t; + + private final AsyncContext ac; + private final ServletInputStream sis; + private final ServletOutputStream sos; + private final byte[] buffer = new byte[8192]; + + public EchoListener(AsyncContext ac, ServletInputStream sis, ServletOutputStream sos) { + t = null; + this.ac = ac; + this.sis = sis; + this.sos = sos; + } + + @Override + public void onWritePossible() throws IOException { + if (sis.isFinished()) { + sos.flush(); + ac.complete(); + return; + } + while (sis.isReady()) { + int read = sis.read(buffer); + if (read > 0) { + sos.write(buffer, 0, read); + if (!sos.isReady()) { + break; + } + } + } + } + + @Override + public void onDataAvailable() throws IOException { + if (sos.isReady()) { + onWritePossible(); + } + } + + @Override + public void onAllDataRead() throws IOException { + if (sos.isReady()) { + onWritePossible(); + } + } + + @Override + public void onError(Throwable throwable) { + t = throwable; + ac.complete(); + } + } +} Propchange: tomcat/trunk/test/org/apache/coyote/TestIoTimeouts.java ------------------------------------------------------------------------------ svn:eol-style = native --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org