https://bz.apache.org/bugzilla/show_bug.cgi?id=68559
Bug ID: 68559 Summary: BadRequestException doesn't send back a 400 when using Async servlets Product: Tomcat 9 Version: 9.0.85 Hardware: PC OS: All Status: NEW Severity: major Priority: P2 Component: Servlet Assignee: dev@tomcat.apache.org Reporter: adwsi...@gmail.com Target Milestone: ----- This is similar to https://bz.apache.org/bugzilla/show_bug.cgi?id=68037, but for BadRequestException. When using Async servlets I am neither able to set a error status nor am I able to get a correct 400 in the response. However it works for read timeouts correctly where I get a 408. Reproducible test : import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; import org.apache.catalina.Wrapper; import org.apache.catalina.connector.ClientAbortException; import org.apache.catalina.connector.Connector; import org.apache.catalina.core.StandardHost; import org.apache.catalina.startup.Tomcat; import org.apache.coyote.BadRequestException; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import javax.servlet.ReadListener; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; import java.net.SocketTimeoutException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; public class ReproducibleTest { static Tomcat tomcat; @BeforeAll static void setup() throws LifecycleException { tomcat = new Tomcat(); ExecutorService executorService = Executors.newFixedThreadPool(5); Context ctx = tomcat.addContext("", new File(".").getAbsolutePath()); Wrapper asyncWrapper = Tomcat.addServlet(ctx, "TestAsync", new TestAsyncServlet()); Wrapper syncWrapper = Tomcat.addServlet(ctx, "TestSync", new SyncServlet()); asyncWrapper.setAsyncSupported(true); StandardHost host = (StandardHost) tomcat.getHost(); host.setErrorReportValveClass(null); Connector connector = new Connector(); connector.setProperty("address", "http://localhost"); connector.setPort(8000); connector.setProperty("connectionTimeout", String.valueOf(100)); connector.getProtocolHandler().setExecutor(executorService); tomcat.getService().addConnector(connector); ctx.addServletMappingDecoded("/async/*", "TestAsync"); ctx.addServletMappingDecoded("/sync/*", "TestSync"); tomcat.start(); } @AfterAll static void destroy() throws LifecycleException { tomcat.stop(); tomcat.destroy(); } @Test @DisplayName("requests that time out while reading the payload get a 408") void timeoutGetsA408() throws Exception { try (Socket s = new Socket("localhost", 8000)) { String request = "GET /sync HTTP/1.1\r\nHost: localhost\r\ncontent-length: 100\r\n\r\n"; sendBadRequest(s, request, 408); } } @Test @DisplayName("requests that time out while reading the payload get a 408") void timeoutGetsA408Async() throws Exception { try (Socket s = new Socket("localhost", 8000)) { String request = "GET /async HTTP/1.1\r\nHost: localhost\r\ncontent-length: 100\r\n\r\n"; sendBadRequest(s, request, 408); } } @Test void badChunksAsync() throws IOException { try (Socket s = new Socket("localhost", 8000)) { String request = "GET /async HTTP/1.1\r\nHost: localhost\r\nTransfer-encoding: chunked\r\n\r\n1\r\na\r\r"; sendBadRequest(s, request, 400); } } @Test void badChunks() throws Exception { try (Socket s = new Socket("localhost", 8000)) { String request = "GET /sync HTTP/1.1\r\nHost: localhost\r\nTransfer-encoding: chunked\r\n\r\n1\r\na\r\r"; sendBadRequest(s, request, 400); } } private static void sendBadRequest(Socket socket, String request, int expectedStatusCode) throws IOException { OutputStream os = socket.getOutputStream(); os.write(request.getBytes(UTF_8)); InputStream is = socket.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is, UTF_8)); String opening = reader.readLine(); assertNotNull(opening, "Didn't get back a response"); StringBuilder sb = new StringBuilder(opening); try { assertTrue(opening.startsWith("HTTP/1.1 " + expectedStatusCode), "expected status code " + expectedStatusCode + " but got " + opening); boolean connectionClose = false; while (reader.ready()) { String line = reader.readLine(); if (line == null) { break; } sb.append("\n").append(line); if ("connection: close".equalsIgnoreCase(line)) { connectionClose = true; } assertFalse(line.contains("Exception Report")); assertFalse(line.contains("Status Report")); } assertTrue(connectionClose, "No 'Connection: close' header seen"); } catch (Throwable t) { fail("Response:\n" + sb, t); } } static final class SyncServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { while (req.getInputStream().read() != -1) ; resp.setStatus(200); resp.flushBuffer(); } catch (ClientAbortException e) { if(!resp.isCommitted()) { resp.sendError(408); } } } } static final class TestAsyncServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.startAsync(); req.getInputStream().setReadListener(new ReadListener() { @Override public void onDataAvailable() throws IOException { while (req.getInputStream().isReady()) { req.getInputStream().read(); } } @Override public void onAllDataRead() throws IOException { resp.setStatus(200); req.getAsyncContext().complete(); } @Override public void onError(Throwable t) { if (t instanceof BadRequestException || t instanceof SocketTimeoutException) { try { resp.setStatus(t instanceof BadRequestException ? 400 : 408); resp.flushBuffer(); } catch (IOException e) { e.printStackTrace(); } } req.getAsyncContext().complete(); } }); } } } -- You are receiving this mail because: You are the assignee for the bug. --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org