Author: markt
Date: Tue Mar 3 09:14:17 2015
New Revision: 1663562
URL: http://svn.apache.org/r1663562
Log:
Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=57621
When an async request completes, need to ensure that any unread input is
swallowed.
Modified:
tomcat/trunk/java/org/apache/catalina/core/AsyncContextImpl.java
tomcat/trunk/java/org/apache/coyote/ActionCode.java
tomcat/trunk/java/org/apache/coyote/ajp/AjpProcessor.java
tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java
tomcat/trunk/test/org/apache/catalina/startup/SimpleHttpClient.java
tomcat/trunk/test/org/apache/coyote/http11/TestHttp11Processor.java
Modified: tomcat/trunk/java/org/apache/catalina/core/AsyncContextImpl.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/AsyncContextImpl.java?rev=1663562&r1=1663561&r2=1663562&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/core/AsyncContextImpl.java (original)
+++ tomcat/trunk/java/org/apache/catalina/core/AsyncContextImpl.java Tue Mar 3
09:14:17 2015
@@ -107,11 +107,15 @@ public class AsyncContextImpl implements
context.unbind(Globals.IS_SECURITY_ENABLED, oldCL);
}
- // The application doesn't know it has to stop writing until it
receives
- // the complete event so the response has to be closed after firing the
- // event.
+ // The application doesn't know it has to stop read and/or writing
until
+ // it receives the complete event so the request and response have to
be
+ // closed after firing the event.
try {
+ // First of all ensure that any data written to the response is
+ // written to the I/O layer.
request.getResponse().finishResponse();
+ // Close the request and the response.
+ request.getCoyoteRequest().action(ActionCode.END_REQUEST, null);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// Catch this here and allow async context complete to continue
Modified: tomcat/trunk/java/org/apache/coyote/ActionCode.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/ActionCode.java?rev=1663562&r1=1663561&r2=1663562&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/ActionCode.java (original)
+++ tomcat/trunk/java/org/apache/coyote/ActionCode.java Tue Mar 3 09:14:17 2015
@@ -228,5 +228,11 @@ public enum ActionCode {
* when the non-blocking listeners are configured on a thread where the
* processing wasn't triggered by a read or write event on the socket.
*/
- DISPATCH_EXECUTE
+ DISPATCH_EXECUTE,
+
+ /**
+ * Trigger end of request processing (remaining input swallowed, write any
+ * remaining parts of the response etc.).
+ */
+ END_REQUEST
}
Modified: tomcat/trunk/java/org/apache/coyote/ajp/AjpProcessor.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/ajp/AjpProcessor.java?rev=1663562&r1=1663561&r2=1663562&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/ajp/AjpProcessor.java (original)
+++ tomcat/trunk/java/org/apache/coyote/ajp/AjpProcessor.java Tue Mar 3
09:14:17 2015
@@ -600,6 +600,9 @@ public class AjpProcessor extends Abstra
setErrorState(ErrorState.CLOSE_NOW, null);
break;
}
+ case END_REQUEST: {
+ // NO-OP for AJP
+ }
}
}
Modified: tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java?rev=1663562&r1=1663561&r2=1663562&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java Tue Mar 3
09:14:17 2015
@@ -974,6 +974,9 @@ public class Http11Processor extends Abs
}
break;
}
+ case END_REQUEST: {
+ endRequest();
+ }
}
}
@@ -1128,22 +1131,12 @@ public class Http11Processor extends Abs
// Finish the handling of the request
rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);
-
if (!isAsync()) {
- if (getErrorState().isError()) {
- // If we know we are closing the connection, don't drain
- // input. This way uploading a 100GB file doesn't tie up
the
- // thread if the servlet has rejected it.
- inputBuffer.setSwallowInput(false);
- } else {
- // Need to check this again here in case the response was
- // committed before the error that requires the connection
- // to be closed occurred.
- checkExpectationAndResponseStatus();
- }
+ // If this is an async request then the request ends when it
has
+ // been completed. The AsyncContext is responsible for calling
+ // endRequest() in that case.
endRequest();
}
-
rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT);
// If there was an error, make sure the request is counted as
@@ -1807,7 +1800,23 @@ public class Http11Processor extends Abs
}
+ /*
+ * No more input will be passed to the application. Remaining input will be
+ * swallowed or the connection dropped depending on the error and
+ * expectation status.
+ */
private void endRequest() {
+ if (getErrorState().isError()) {
+ // If we know we are closing the connection, don't drain
+ // input. This way uploading a 100GB file doesn't tie up the
+ // thread if the servlet has rejected it.
+ inputBuffer.setSwallowInput(false);
+ } else {
+ // Need to check this again here in case the response was
+ // committed before the error that requires the connection
+ // to be closed occurred.
+ checkExpectationAndResponseStatus();
+ }
// Finish the handling of the request
if (getErrorState().isIoAllowed()) {
Modified: tomcat/trunk/test/org/apache/catalina/startup/SimpleHttpClient.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/startup/SimpleHttpClient.java?rev=1663562&r1=1663561&r2=1663562&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/catalina/startup/SimpleHttpClient.java
(original)
+++ tomcat/trunk/test/org/apache/catalina/startup/SimpleHttpClient.java Tue Mar
3 09:14:17 2015
@@ -385,6 +385,10 @@ public abstract class SimpleHttpClient {
useContinue = false;
+ resetResponse();
+ }
+
+ public void resetResponse() {
responseLine = null;
responseHeaders = new ArrayList<>();
responseBody = null;
Modified: tomcat/trunk/test/org/apache/coyote/http11/TestHttp11Processor.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http11/TestHttp11Processor.java?rev=1663562&r1=1663561&r2=1663562&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/coyote/http11/TestHttp11Processor.java
(original)
+++ tomcat/trunk/test/org/apache/coyote/http11/TestHttp11Processor.java Tue Mar
3 09:14:17 2015
@@ -45,6 +45,7 @@ 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.TesterServlet;
import org.apache.catalina.startup.Tomcat;
@@ -701,4 +702,103 @@ public class TestHttp11Processor extends
return getResponseBody().contains("test - data");
}
}
+
+
+ /*
+ * Partially read chunked input is not swallowed when it is read during
+ * async processing.
+ */
+ @Test
+ public void testBug57621() throws Exception {
+
+ Tomcat tomcat = getTomcatInstance();
+ Context root = tomcat.addContext("", null);
+ Wrapper w = Tomcat.addServlet(root, "Bug57621", new Bug57621Servlet());
+ w.setAsyncSupported(true);
+ root.addServletMapping("/test", "Bug57621");
+
+ tomcat.start();
+
+ Bug57621Client client = new Bug57621Client();
+ client.setPort(tomcat.getConnector().getLocalPort());
+
+ client.setUseContentLength(true);
+
+ client.connect();
+
+ client.doRequest();
+ assertTrue(client.getResponseLine(), client.isResponse200());
+ assertTrue(client.isResponseBodyOK());
+
+ // Do the request again to ensure that the remaining body was swallowed
+ client.resetResponse();
+ client.processRequest();
+ assertTrue(client.isResponse200());
+ assertTrue(client.isResponseBodyOK());
+
+ client.disconnect();
+ }
+
+
+ private static class Bug57621Servlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doPut(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+ AsyncContext ac = req.startAsync();
+ ac.start(new Runnable() {
+ @Override
+ public void run() {
+ resp.setContentType("text/plain");
+ resp.setCharacterEncoding("UTF-8");
+ try {
+ resp.getWriter().print("OK");
+ } catch (IOException e) {
+ // Should never happen. Test will fail if it does.
+ }
+ ac.complete();
+ }
+ });
+ }
+ }
+
+
+ private class Bug57621Client extends SimpleHttpClient {
+
+ private Exception doRequest() {
+ try {
+ String[] request = new String[2];
+ request[0] =
+ "PUT http://localhost:8080/test HTTP/1.1" + CRLF +
+ "Transfer-encoding: chunked" + CRLF +
+ CRLF +
+ "2" + CRLF +
+ "OK";
+
+ request[1] =
+ CRLF +
+ "0" + CRLF +
+ CRLF;
+
+ setRequest(request);
+ processRequest(); // blocks until response has been read
+ } catch (Exception e) {
+ return e;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isResponseBodyOK() {
+ if (getResponseBody() == null) {
+ return false;
+ }
+ if (!getResponseBody().contains("OK")) {
+ return false;
+ }
+ return true;
+ }
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]