You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2016/06/02 13:08:38 UTC

svn commit: r1746554 - /tomcat/trunk/test/org/apache/coyote/TestIoTimeouts.java

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-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org