diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index 3379981c827..610db58cbf9 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -24,7 +24,6 @@ import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritePendingException; import java.util.concurrent.atomic.AtomicReference; - import javax.servlet.RequestDispatcher; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; @@ -885,7 +884,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable // Can we just aggregate the remainder? if (!_complete && _len parameters() + { + return Arrays.asList(new SslContextFactory[]{null}, new SslContextFactory[]{new SslContextFactory()}); + } + + private Server server; + private ServerConnector connector; + private SslContextFactory sslContextFactory; + private String contextPath; + private String servletPath; + + public SSLAsyncIOServletTest(SslContextFactory sslContextFactory) + { + this.sslContextFactory = sslContextFactory; + if (sslContextFactory != null) + { + sslContextFactory.setEndpointIdentificationAlgorithm(""); + sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); + sslContextFactory.setKeyStorePassword("storepwd"); + sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks"); + sslContextFactory.setTrustStorePassword("storepwd"); + } + } + + public void prepare(HttpServlet servlet) throws Exception + { + server = new Server(); + + connector = new ServerConnector(server, sslContextFactory); + server.addConnector(connector); + + contextPath = "/context"; + ServletContextHandler context = new ServletContextHandler(server, contextPath, true, false); + servletPath = "/servlet"; + context.addServlet(new ServletHolder(servlet), servletPath); + + server.start(); + } + + @After + public void dispose() throws Exception + { + server.stop(); + } + + @Test + public void testAsyncIOWritesWithAggregation() throws Exception + { + Random random = new Random(); + String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + final byte[] content = new byte[50000]; + for (int i = 0; i < content.length; ++i) + content[i] = (byte)chars.charAt(random.nextInt(chars.length())); + + prepare(new HttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + final AsyncContext asyncContext = request.startAsync(); + asyncContext.setTimeout(0); + final int bufferSize = 4096; + response.setBufferSize(bufferSize); + response.getOutputStream().setWriteListener(new WriteListener() + { + private int writes; + private int written; + + @Override + public void onWritePossible() throws IOException + { + ServletOutputStream output = asyncContext.getResponse().getOutputStream(); + do + { + int toWrite = content.length - written; + if (toWrite == 0) + { + asyncContext.complete(); + return; + } + + toWrite = Math.min(toWrite, bufferSize); + + // Perform a write that is smaller than the buffer size to + // trigger the condition where the bytes are aggregated. + if (writes == 1) + toWrite -= 16; + + output.write(content, written, toWrite); + ++writes; + written += toWrite; + } + while (output.isReady()); + } + + @Override + public void onError(Throwable t) + { + asyncContext.complete(); + } + }); + } + }); + + try (Socket client = newClient()) + { + String request = "" + + "GET " + contextPath + servletPath + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n"; + OutputStream output = client.getOutputStream(); + output.write(request.getBytes("UTF-8")); + output.flush(); + + BufferedReader input = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); + SimpleHttpParser parser = new SimpleHttpParser(); + SimpleHttpResponse response = parser.readResponse(input); + Assert.assertEquals("200", response.getCode()); + Assert.assertArrayEquals(content, response.getBody().getBytes("UTF-8")); + } + } + + private Socket newClient() throws IOException + { + return sslContextFactory == null ? new Socket("localhost", connector.getLocalPort()) + : sslContextFactory.getSslContext().getSocketFactory().createSocket("localhost", connector.getLocalPort()); + } +} diff --git a/jetty-servlet/src/test/resources/keystore.jks b/jetty-servlet/src/test/resources/keystore.jks new file mode 100644 index 00000000000..428ba54776e Binary files /dev/null and b/jetty-servlet/src/test/resources/keystore.jks differ diff --git a/jetty-servlet/src/test/resources/truststore.jks b/jetty-servlet/src/test/resources/truststore.jks new file mode 100644 index 00000000000..839cb8c3515 Binary files /dev/null and b/jetty-servlet/src/test/resources/truststore.jks differ