// // ======================================================================== // Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.spdy.server.http; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.spdy.api.BytesDataInfo; import org.eclipse.jetty.spdy.api.DataInfo; import org.eclipse.jetty.spdy.api.ReplyInfo; import org.eclipse.jetty.spdy.api.Session; import org.eclipse.jetty.spdy.api.Stream; import org.eclipse.jetty.spdy.api.StreamFrameListener; import org.eclipse.jetty.spdy.api.StringDataInfo; import org.eclipse.jetty.spdy.api.SynInfo; import org.eclipse.jetty.spdy.http.HTTPSPDYHeader; import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.log.StdErrLog; import org.junit.Assert; import org.junit.Test; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest { public static final String SUSPENDED_ATTRIBUTE = ServerHTTPSPDYTest.class.getName() + ".SUSPENDED"; public ServerHTTPSPDYTest(short version) { super(version); } @Test public void testSimpleGET() throws Exception { final String path = "/foo"; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); assertEquals("GET", httpRequest.getMethod()); assertEquals(path, target); assertEquals(path, httpRequest.getRequestURI()); assertThat("accept-encoding is set to gzip, even if client didn't set it", httpRequest.getHeader("accept-encoding"), containsString("gzip")); assertThat(httpRequest.getHeader("host"), is("localhost:" + connector.getLocalPort())); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getLocalPort(), version, "GET", path); final CountDownLatch replyLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { assertTrue(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertThat(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"), is(true)); assertThat(replyHeaders.get(HttpHeader.SERVER.asString()), is(notNullValue())); assertThat(replyHeaders.get(HttpHeader.X_POWERED_BY.asString()), is(notNullValue())); replyLatch.countDown(); } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithQueryString() throws Exception { final String path = "/foo"; final String query = "p=1"; final String uri = path + "?" + query; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); assertEquals("GET", httpRequest.getMethod()); assertEquals(path, target); assertEquals(path, httpRequest.getRequestURI()); assertEquals(query, httpRequest.getQueryString()); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", uri); final CountDownLatch replyLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { assertTrue(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithCookies() throws Exception { final String path = "/foo"; final String uri = path; final String cookie1 = "cookie1"; final String cookie2 = "cookie2"; final String cookie1Value = "cookie 1 value"; final String cookie2Value = "cookie 2 value"; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); httpResponse.addCookie(new Cookie(cookie1, cookie1Value)); httpResponse.addCookie(new Cookie(cookie2, cookie2Value)); assertThat("method is GET", httpRequest.getMethod(), is("GET")); assertThat("target is /foo", target, is(path)); assertThat("requestUri is /foo", httpRequest.getRequestURI(), is(path)); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", uri); final CountDownLatch replyLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { assertThat("isClose is true", replyInfo.isClose(), is(true)); Fields replyHeaders = replyInfo.getHeaders(); assertThat("response code is 200 OK", replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue() .contains("200"), is(true)); assertThat(replyInfo.getHeaders().get("Set-Cookie").getValues().get(0), is(cookie1 + "=\"" + cookie1Value + "\";Version=1")); assertThat(replyInfo.getHeaders().get("Set-Cookie").getValues().get(1), is(cookie2 + "=\"" + cookie2Value + "\";Version=1")); replyLatch.countDown(); } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); } @Test public void testHEAD() throws Exception { final String path = "/foo"; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); assertEquals("HEAD", httpRequest.getMethod()); assertEquals(path, target); assertEquals(path, httpRequest.getRequestURI()); httpResponse.getWriter().write("body that shouldn't be sent on a HEAD request"); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "HEAD", path); final CountDownLatch replyLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { assertTrue(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { fail("HEAD request shouldn't send any data"); } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); } @Test public void testPOSTWithDelayedContentBody() throws Exception { final String path = "/foo"; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { // don't read the request body, reply immediately request.setHandled(true); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", path); headers.put("content-type", "application/x-www-form-urlencoded"); final CountDownLatch replyLatch = new CountDownLatch(1); Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { assertTrue(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } }); stream.data(new StringDataInfo("a", false)); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); stream.data(new StringDataInfo("b", true)); } @Test public void testPOSTWithParameters() throws Exception { final String path = "/foo"; final String data = "a=1&b=2"; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); assertEquals("POST", httpRequest.getMethod()); assertEquals("1", httpRequest.getParameter("a")); assertEquals("2", httpRequest.getParameter("b")); assertNotNull(httpRequest.getRemoteHost()); assertNotNull(httpRequest.getRemotePort()); assertNotNull(httpRequest.getRemoteAddr()); assertNotNull(httpRequest.getLocalPort()); assertNotNull(httpRequest.getLocalName()); assertNotNull(httpRequest.getLocalAddr()); assertNotNull(httpRequest.getServerPort()); assertNotNull(httpRequest.getServerName()); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", path); headers.put("content-type", "application/x-www-form-urlencoded"); final CountDownLatch replyLatch = new CountDownLatch(1); Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { assertTrue(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } }); stream.data(new StringDataInfo(data, true)); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); } @Test public void testPOSTWithParametersInTwoFramesTwoReads() throws Exception { final String path = "/foo"; final String data1 = "a=1&"; final String data2 = "b=2"; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); assertEquals("POST", httpRequest.getMethod()); assertEquals("1", httpRequest.getParameter("a")); assertEquals("2", httpRequest.getParameter("b")); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", path); headers.put("content-type", "application/x-www-form-urlencoded"); final CountDownLatch replyLatch = new CountDownLatch(1); Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { assertTrue(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } }); // Sleep between the data frames so that they will be read in 2 reads stream.data(new StringDataInfo(data1, false)); Thread.sleep(1000); stream.data(new StringDataInfo(data2, true)); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); } @Test public void testPOSTWithParametersInTwoFramesOneRead() throws Exception { final String path = "/foo"; final String data1 = "a=1&"; final String data2 = "b=2"; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); assertEquals("POST", httpRequest.getMethod()); assertEquals("1", httpRequest.getParameter("a")); assertEquals("2", httpRequest.getParameter("b")); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", path); headers.put("content-type", "application/x-www-form-urlencoded"); final CountDownLatch replyLatch = new CountDownLatch(1); Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { assertTrue(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.toString(), replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } }); // Send the data frames consecutively, so the server reads both frames in one read stream.data(new StringDataInfo(data1, false)); stream.data(new StringDataInfo(data2, true)); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithSmallResponseContent() throws Exception { final String data = "0123456789ABCDEF"; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); httpResponse.setStatus(HttpServletResponse.SC_OK); ServletOutputStream output = httpResponse.getOutputStream(); output.write(data.getBytes(StandardCharsets.UTF_8)); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Assert.assertFalse(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { assertTrue(dataInfo.isClose()); assertEquals(data, dataInfo.asString(StandardCharsets.UTF_8, true)); dataLatch.countDown(); } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithOneByteResponseContent() throws Exception { final char data = 'x'; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); httpResponse.setStatus(HttpServletResponse.SC_OK); ServletOutputStream output = httpResponse.getOutputStream(); output.write(data); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Assert.assertFalse(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { assertTrue(dataInfo.isClose()); byte[] bytes = dataInfo.asBytes(true); assertEquals(1, bytes.length); assertEquals(data, bytes[0]); dataLatch.countDown(); } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithSmallResponseContentInTwoChunks() throws Exception { final String data1 = "0123456789ABCDEF"; final String data2 = "FEDCBA9876543210"; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); httpResponse.setStatus(HttpServletResponse.SC_OK); ServletOutputStream output = httpResponse.getOutputStream(); output.write(data1.getBytes(StandardCharsets.UTF_8)); output.flush(); output.write(data2.getBytes(StandardCharsets.UTF_8)); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(2); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { private final AtomicInteger replyFrames = new AtomicInteger(); private final AtomicInteger dataFrames = new AtomicInteger(); @Override public void onReply(Stream stream, ReplyInfo replyInfo) { assertEquals(1, replyFrames.incrementAndGet()); Assert.assertFalse(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { int data = dataFrames.incrementAndGet(); assertTrue(data >= 1 && data <= 2); if (data == 1) assertEquals(data1, dataInfo.asString(StandardCharsets.UTF_8, true)); else assertEquals(data2, dataInfo.asString(StandardCharsets.UTF_8, true)); dataLatch.countDown(); } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithBigResponseContentInOneWrite() throws Exception { final byte[] data = new byte[128 * 1024]; Arrays.fill(data, (byte)'x'); final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); httpResponse.setStatus(HttpServletResponse.SC_OK); ServletOutputStream output = httpResponse.getOutputStream(); output.write(data); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { private final AtomicInteger contentBytes = new AtomicInteger(); @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Assert.assertFalse(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { contentBytes.addAndGet(dataInfo.asByteBuffer(true).remaining()); if (dataInfo.isClose()) { assertEquals(data.length, contentBytes.get()); dataLatch.countDown(); } } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithBigResponseContentInMultipleWrites() throws Exception { final byte[] data = new byte[4 * 1024]; Arrays.fill(data, (byte)'x'); final int writeTimes = 16; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); httpResponse.setStatus(HttpServletResponse.SC_OK); ServletOutputStream output = httpResponse.getOutputStream(); for (int i = 0; i < writeTimes; i++) { output.write(data); } handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { private final AtomicInteger contentBytes = new AtomicInteger(); @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Assert.assertFalse(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { contentBytes.addAndGet(dataInfo.asByteBuffer(true).remaining()); if (dataInfo.isClose()) { assertEquals(data.length * writeTimes, contentBytes.get()); dataLatch.countDown(); } } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithBigResponseContentInTwoWrites() throws Exception { final byte[] data = new byte[128 * 1024]; Arrays.fill(data, (byte)'y'); final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); httpResponse.setStatus(HttpServletResponse.SC_OK); ServletOutputStream output = httpResponse.getOutputStream(); output.write(data); output.write(data); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { private final AtomicInteger contentBytes = new AtomicInteger(); @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Assert.assertFalse(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { contentBytes.addAndGet(dataInfo.asByteBuffer(true).remaining()); if (dataInfo.isClose()) { assertEquals(2 * data.length, contentBytes.get()); dataLatch.countDown(); } } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithOutputStreamFlushedAndClosed() throws Exception { final String data = "0123456789ABCDEF"; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); httpResponse.setStatus(HttpServletResponse.SC_OK); ServletOutputStream output = httpResponse.getOutputStream(); output.write(data.getBytes(StandardCharsets.UTF_8)); output.flush(); output.close(); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Assert.assertFalse(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { ByteBuffer byteBuffer = dataInfo.asByteBuffer(true); while (byteBuffer.hasRemaining()) buffer.write(byteBuffer.get()); if (dataInfo.isClose()) { assertEquals(data, new String(buffer.toByteArray(), StandardCharsets.UTF_8)); dataLatch.countDown(); } } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithResponseResetBuffer() throws Exception { final String data1 = "0123456789ABCDEF"; final String data2 = "FEDCBA9876543210"; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); httpResponse.setStatus(HttpServletResponse.SC_OK); ServletOutputStream output = httpResponse.getOutputStream(); // Write some output.write(data1.getBytes(StandardCharsets.UTF_8)); // But then change your mind and reset the buffer httpResponse.resetBuffer(); output.write(data2.getBytes(StandardCharsets.UTF_8)); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Assert.assertFalse(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { ByteBuffer byteBuffer = dataInfo.asByteBuffer(true); while (byteBuffer.hasRemaining()) buffer.write(byteBuffer.get()); if (dataInfo.isClose()) { assertEquals(data2, new String(buffer.toByteArray(), StandardCharsets.UTF_8)); dataLatch.countDown(); } } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithRedirect() throws Exception { final String suffix = "/redirect"; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); String location = httpResponse.encodeRedirectURL(String.format("%s://%s:%d%s", request.getScheme(), request.getLocalAddr(), request.getLocalPort(), target + suffix)); httpResponse.sendRedirect(location); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { private final AtomicInteger replies = new AtomicInteger(); @Override public void onReply(Stream stream, ReplyInfo replyInfo) { assertEquals(1, replies.incrementAndGet()); assertTrue(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("302")); assertTrue(replyHeaders.get("location").getValue().endsWith(suffix)); replyLatch.countDown(); } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithSendError() throws Exception { final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { private final AtomicInteger replies = new AtomicInteger(); @Override public void onReply(Stream stream, ReplyInfo replyInfo) { assertEquals(1, replies.incrementAndGet()); Assert.assertFalse(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("404")); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { if (dataInfo.isClose()) dataLatch.countDown(); } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithException() throws Exception { StdErrLog log = StdErrLog.getLogger(HttpChannel.class); log.setHideStacks(true); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { throw new NullPointerException("thrown_explicitly_by_the_test"); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { private final AtomicInteger replies = new AtomicInteger(); @Override public void onReply(Stream stream, ReplyInfo replyInfo) { assertEquals(1, replies.incrementAndGet()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("500")); replyLatch.countDown(); if (replyInfo.isClose()) latch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { if (dataInfo.isClose()) latch.countDown(); } }); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); assertTrue(latch.await(5, TimeUnit.SECONDS)); log.setHideStacks(false); } @Test public void testGETWithSmallResponseContentChunked() throws Exception { final String pangram1 = "the quick brown fox jumps over the lazy dog"; final String pangram2 = "qualche vago ione tipo zolfo, bromo, sodio"; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); httpResponse.setHeader("Transfer-Encoding", "chunked"); ServletOutputStream output = httpResponse.getOutputStream(); output.write(pangram1.getBytes(StandardCharsets.UTF_8)); httpResponse.setHeader("EXTRA", "X"); output.flush(); output.write(pangram2.getBytes(StandardCharsets.UTF_8)); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(2); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { private final AtomicInteger replyFrames = new AtomicInteger(); private final AtomicInteger dataFrames = new AtomicInteger(); @Override public void onReply(Stream stream, ReplyInfo replyInfo) { assertEquals(1, replyFrames.incrementAndGet()); Assert.assertFalse(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); assertTrue(replyHeaders.get("extra").getValue().contains("X")); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { int count = dataFrames.incrementAndGet(); if (count == 1) { Assert.assertFalse(dataInfo.isClose()); assertEquals(pangram1, dataInfo.asString(StandardCharsets.UTF_8, true)); } else if (count == 2) { assertTrue(dataInfo.isClose()); assertEquals(pangram2, dataInfo.asString(StandardCharsets.UTF_8, true)); } dataLatch.countDown(); } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithMediumContentAsBufferByPassed() throws Exception { final byte[] data = new byte[2048]; final CountDownLatch handlerLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); request.getResponse().getHttpOutput().sendContent(ByteBuffer.wrap(data)); handlerLatch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { private final AtomicInteger replyFrames = new AtomicInteger(); private final AtomicInteger contentLength = new AtomicInteger(); @Override public void onReply(Stream stream, ReplyInfo replyInfo) { assertEquals(1, replyFrames.incrementAndGet()); Assert.assertFalse(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { contentLength.addAndGet(dataInfo.asBytes(true).length); if (dataInfo.isClose()) { Assert.assertEquals(data.length, contentLength.get()); dataLatch.countDown(); } } }); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } @Test public void testGETWithMultipleMediumContentByPassed() throws Exception { final byte[] data = new byte[2048]; Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { // The sequence of write/flush/write/write below triggers a condition where // HttpGenerator._bypass is set to true on the second write(), and the // third write causes an infinite spin loop on the third write(). request.setHandled(true); OutputStream output = httpResponse.getOutputStream(); output.write(data); output.flush(); output.write(data); output.write(data); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(1); final AtomicInteger contentLength = new AtomicInteger(); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Assert.assertFalse(replyInfo.isClose()); Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { dataInfo.consume(dataInfo.available()); contentLength.addAndGet(dataInfo.length()); if (dataInfo.isClose()) dataLatch.countDown(); } }); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); assertEquals(3 * data.length, contentLength.get()); } @Test public void testPOSTThenSuspendRequestThenReadOneChunkThenComplete() throws Exception { final byte[] data = new byte[2000]; final CountDownLatch latch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); final AsyncContext asyncContext = request.startAsync(); new Thread() { @Override public void run() { try { readRequestData(request, data.length); asyncContext.complete(); latch.countDown(); } catch (IOException x) { x.printStackTrace(); } } }.start(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } }); stream.data(new BytesDataInfo(data, true)); assertTrue(latch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); } @Test public void testPOSTThenSuspendExpire() throws Exception { final CountDownLatch dispatchedAgainAfterExpire = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); if (request.getAttribute(SUSPENDED_ATTRIBUTE) == Boolean.TRUE) { dispatchedAgainAfterExpire.countDown(); } else { AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(1000); asyncContext.addListener(new AsyncListenerAdapter()); request.setAttribute(SUSPENDED_ATTRIBUTE, Boolean.TRUE); } } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, true, (byte)0), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } }); assertTrue("Not dispatched again after expire", dispatchedAgainAfterExpire.await(5, TimeUnit.SECONDS)); assertTrue("Reply not sent", replyLatch.await(5, TimeUnit.SECONDS)); } @Test public void testPOSTThenSuspendExpireWithRequestData() throws Exception { final byte[] data = new byte[2000]; final CountDownLatch dispatchedAgainAfterExpire = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); if (request.getAttribute(SUSPENDED_ATTRIBUTE) == Boolean.TRUE) { dispatchedAgainAfterExpire.countDown(); } else { readRequestData(request, data.length); AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(1000); asyncContext.addListener(new AsyncListenerAdapter()); request.setAttribute(SUSPENDED_ATTRIBUTE, Boolean.TRUE); } } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } }); stream.data(new BytesDataInfo(data, true)); assertTrue("Not dispatched again after expire", dispatchedAgainAfterExpire.await(5, TimeUnit.SECONDS)); assertTrue("Reply not sent", replyLatch.await(5, TimeUnit.SECONDS)); } private void readRequestData(Request request, int expectedDataLength) throws IOException { InputStream input = request.getInputStream(); byte[] buffer = new byte[512]; int read = 0; while (read < expectedDataLength) read += input.read(buffer); } @Test public void testPOSTThenSuspendRequestThenReadTwoChunksThenComplete() throws Exception { final byte[] data = new byte[2000]; final CountDownLatch latch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); final AsyncContext asyncContext = request.startAsync(); new Thread() { @Override public void run() { try { InputStream input = request.getInputStream(); byte[] buffer = new byte[512]; int read = 0; while (read < 2 * data.length) read += input.read(buffer); asyncContext.complete(); latch.countDown(); } catch (IOException x) { x.printStackTrace(); } } }.start(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); replyLatch.countDown(); } }); stream.data(new BytesDataInfo(data, false)); stream.data(new BytesDataInfo(data, true)); assertTrue(latch.await(5, TimeUnit.SECONDS)); assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); } @Test public void testPOSTThenSuspendRequestThenResumeThenRespond() throws Exception { final byte[] data = new byte[1000]; final CountDownLatch latch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); if (request.getAttribute(SUSPENDED_ATTRIBUTE) == Boolean.TRUE) { OutputStream output = httpResponse.getOutputStream(); output.write(data); } else { final AsyncContext asyncContext = request.startAsync(); request.setAttribute(SUSPENDED_ATTRIBUTE, Boolean.TRUE); InputStream input = request.getInputStream(); byte[] buffer = new byte[256]; int read = 0; while (read < data.length) read += input.read(buffer); new Thread() { @Override public void run() { try { TimeUnit.SECONDS.sleep(1); asyncContext.dispatch(); latch.countDown(); } catch (InterruptedException x) { x.printStackTrace(); } } }.start(); } } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo"); final CountDownLatch responseLatch = new CountDownLatch(2); Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); responseLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { if (dataInfo.isClose()) responseLatch.countDown(); } }); stream.data(new BytesDataInfo(data, true)); assertTrue(latch.await(5, TimeUnit.SECONDS)); assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); } @Test public void testPOSTThenResponseWithoutReadingContent() throws Exception { final byte[] data = new byte[1000]; final CountDownLatch latch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { request.setHandled(true); latch.countDown(); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo"); final CountDownLatch responseLatch = new CountDownLatch(1); Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Fields replyHeaders = replyInfo.getHeaders(); assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200")); responseLatch.countDown(); } }); stream.data(new BytesDataInfo(data, false)); stream.data(new BytesDataInfo(5, TimeUnit.SECONDS, data, true)); assertTrue(latch.await(5, TimeUnit.SECONDS)); assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); } @Test public void testIdleTimeout() throws Exception { final int idleTimeout = 500; final CountDownLatch timeoutReceivedLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { try { Thread.sleep(2 * idleTimeout); } catch (InterruptedException e) { throw new RuntimeException(e); } request.setHandled(true); } }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/"); Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, true, (byte)0), new StreamFrameListener.Adapter() { @Override public void onFailure(Stream stream, Throwable x) { assertThat("we got a TimeoutException", x, instanceOf(TimeoutException.class)); timeoutReceivedLatch.countDown(); } }); stream.setIdleTimeout(idleTimeout); assertThat("idle timeout hit", timeoutReceivedLatch.await(5, TimeUnit.SECONDS), is(true)); } @Test public void testIdleTimeoutSetOnConnectionOnly() throws Exception { final int idleTimeout = 500; final CountDownLatch timeoutReceivedLatch = new CountDownLatch(1); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { try { Thread.sleep(2 * idleTimeout); } catch (InterruptedException e) { throw new RuntimeException(e); } request.setHandled(true); } }, idleTimeout), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/"); session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, true, (byte)0), new StreamFrameListener.Adapter() { @Override public void onFailure(Stream stream, Throwable x) { assertThat("we got a TimeoutException", x, instanceOf(TimeoutException.class)); timeoutReceivedLatch.countDown(); } }); assertThat("idle timeout hit", timeoutReceivedLatch.await(5, TimeUnit.SECONDS), is(true)); } @Test public void testSingleStreamIdleTimeout() throws Exception { final int idleTimeout = 500; final CountDownLatch timeoutReceivedLatch = new CountDownLatch(1); final CountDownLatch replyReceivedLatch = new CountDownLatch(3); Session session = startClient(version, startHTTPServer(version, new AbstractHandler() { @Override public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { if ("true".equals(request.getHeader("slow"))) { try { Thread.sleep(2 * idleTimeout); } catch (InterruptedException e) { throw new RuntimeException(e); } } request.setHandled(true); } }, idleTimeout), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/"); Fields slowHeaders = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/"); slowHeaders.add("slow", "true"); sendSingleRequestThatIsNotExpectedToTimeout(replyReceivedLatch, session, headers); session.syn(new SynInfo(5, TimeUnit.SECONDS, slowHeaders, true, (byte)0), new StreamFrameListener.Adapter() { @Override public void onFailure(Stream stream, Throwable x) { assertThat("we got a TimeoutException", x, instanceOf(TimeoutException.class)); timeoutReceivedLatch.countDown(); } }); Thread.sleep(idleTimeout / 2); sendSingleRequestThatIsNotExpectedToTimeout(replyReceivedLatch, session, headers); Thread.sleep(idleTimeout / 2); sendSingleRequestThatIsNotExpectedToTimeout(replyReceivedLatch, session, headers); assertThat("idle timeout hit", timeoutReceivedLatch.await(5, TimeUnit.SECONDS), is(true)); assertThat("received replies on 3 non idle requests", replyReceivedLatch.await(5, TimeUnit.SECONDS), is(true)); } private void sendSingleRequestThatIsNotExpectedToTimeout(final CountDownLatch replyReceivedLatch, Session session, Fields headers) throws ExecutionException, InterruptedException, TimeoutException { session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, true, (byte)0), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { replyReceivedLatch.countDown(); } }); } private class AsyncListenerAdapter implements AsyncListener { @Override public void onStartAsync(AsyncEvent event) throws IOException { } @Override public void onComplete(AsyncEvent event) throws IOException { } @Override public void onTimeout(AsyncEvent event) throws IOException { event.getAsyncContext().dispatch(); } @Override public void onError(AsyncEvent event) throws IOException { } } }