@Test
  public void testProxyRequestFailureInTheMiddleOfProxyingSmallContent() throws Exception {
    final long proxyTimeout = 1000;
    Map<String, String> proxyParams = new HashMap<>();
    proxyParams.put("timeout", String.valueOf(proxyTimeout));
    prepareProxy(proxyParams);

    final CountDownLatch chunk1Latch = new CountDownLatch(1);
    final int chunk1 = 'q';
    final int chunk2 = 'w';
    prepareServer(
        new HttpServlet() {
          @Override
          protected void service(HttpServletRequest request, HttpServletResponse response)
              throws ServletException, IOException {
            ServletOutputStream output = response.getOutputStream();
            output.write(chunk1);
            response.flushBuffer();

            // Wait for the client to receive this chunk.
            await(chunk1Latch, 5000);

            // Send second chunk, must not be received by proxy.
            output.write(chunk2);
          }

          private boolean await(CountDownLatch latch, long ms) throws IOException {
            try {
              return latch.await(ms, TimeUnit.MILLISECONDS);
            } catch (InterruptedException x) {
              throw new InterruptedIOException();
            }
          }
        });

    HttpClient client = prepareClient();
    InputStreamResponseListener listener = new InputStreamResponseListener();
    int port = serverConnector.getLocalPort();
    client.newRequest("localhost", port).send(listener);

    // Make the proxy request fail; given the small content, the
    // proxy-to-client response is not committed yet so it will be reset.
    TimeUnit.MILLISECONDS.sleep(2 * proxyTimeout);

    Response response = listener.get(5, TimeUnit.SECONDS);
    Assert.assertEquals(504, response.getStatus());

    // Make sure there is no content, as the proxy-to-client response has been reset.
    InputStream input = listener.getInputStream();
    Assert.assertEquals(-1, input.read());

    chunk1Latch.countDown();

    // Result succeeds because a 504 is a valid HTTP response.
    Result result = listener.await(5, TimeUnit.SECONDS);
    Assert.assertTrue(result.isSucceeded());

    // Make sure the proxy does not receive chunk2.
    Assert.assertEquals(-1, input.read());

    HttpDestinationOverHTTP destination =
        (HttpDestinationOverHTTP) client.getDestination("http", "localhost", port);
    Assert.assertEquals(0, destination.getConnectionPool().getIdleConnections().size());
  }
  @Test
  public void testProxyRequestFailureInTheMiddleOfProxyingBigContent() throws Exception {
    final long proxyTimeout = 1000;
    int outputBufferSize = 1024;
    Map<String, String> proxyParams = new HashMap<>();
    proxyParams.put("timeout", String.valueOf(proxyTimeout));
    proxyParams.put("outputBufferSize", String.valueOf(outputBufferSize));
    prepareProxy(proxyParams);

    final CountDownLatch chunk1Latch = new CountDownLatch(1);
    final byte[] chunk1 = new byte[outputBufferSize];
    new Random().nextBytes(chunk1);
    final int chunk2 = 'w';
    prepareServer(
        new HttpServlet() {
          @Override
          protected void service(HttpServletRequest request, HttpServletResponse response)
              throws ServletException, IOException {
            ServletOutputStream output = response.getOutputStream();
            output.write(chunk1);
            response.flushBuffer();

            // Wait for the client to receive this chunk.
            await(chunk1Latch, 5000);

            // Send second chunk, must not be received by proxy.
            output.write(chunk2);
          }

          private boolean await(CountDownLatch latch, long ms) throws IOException {
            try {
              return latch.await(ms, TimeUnit.MILLISECONDS);
            } catch (InterruptedException x) {
              throw new InterruptedIOException();
            }
          }
        });

    HttpClient client = prepareClient();
    InputStreamResponseListener listener = new InputStreamResponseListener();
    int port = serverConnector.getLocalPort();
    client.newRequest("localhost", port).send(listener);

    Response response = listener.get(5, TimeUnit.SECONDS);
    Assert.assertEquals(200, response.getStatus());

    InputStream input = listener.getInputStream();
    for (int i = 0; i < chunk1.length; ++i) Assert.assertEquals(chunk1[i] & 0xFF, input.read());

    TimeUnit.MILLISECONDS.sleep(2 * proxyTimeout);

    chunk1Latch.countDown();

    try {
      // Make sure the proxy does not receive chunk2.
      input.read();
      Assert.fail();
    } catch (EOFException x) {
      // Expected
    }

    HttpDestinationOverHTTP destination =
        (HttpDestinationOverHTTP) client.getDestination("http", "localhost", port);
    Assert.assertEquals(0, destination.getConnectionPool().getIdleConnections().size());
  }
  private void testServerSendsConnectionClose(boolean chunked, String content) throws Exception {
    ServerSocket server = new ServerSocket(0);
    int port = server.getLocalPort();

    startClient();

    Request request = client.newRequest("localhost", port).scheme("https").path("/ctx/path");
    FutureResponseListener listener = new FutureResponseListener(request);
    request.send(listener);

    Socket socket = server.accept();
    SSLContext sslContext = client.getSslContextFactory().getSslContext();
    SSLSocket sslSocket =
        (SSLSocket) sslContext.getSocketFactory().createSocket(socket, "localhost", port, false);
    sslSocket.setUseClientMode(false);
    sslSocket.startHandshake();

    InputStream input = sslSocket.getInputStream();
    consumeRequest(input);

    OutputStream output = sslSocket.getOutputStream();
    String serverResponse = "" + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n";
    if (chunked) {
      serverResponse += "" + "Transfer-Encoding: chunked\r\n" + "\r\n";
      for (int i = 0; i < 2; ++i) {
        serverResponse += Integer.toHexString(content.length()) + "\r\n" + content + "\r\n";
      }
      serverResponse += "" + "0\r\n" + "\r\n";
    } else {
      serverResponse += "Content-Length: " + content.length() + "\r\n";
      serverResponse += "\r\n";
      serverResponse += content;
    }

    output.write(serverResponse.getBytes("UTF-8"));
    output.flush();

    switch (closeMode) {
      case NONE:
        {
          break;
        }
      case CLOSE:
        {
          sslSocket.close();
          break;
        }
      case ABRUPT:
        {
          socket.shutdownOutput();
          break;
        }
      default:
        {
          throw new IllegalStateException();
        }
    }

    ContentResponse response = listener.get(5, TimeUnit.SECONDS);
    Assert.assertEquals(HttpStatus.OK_200, response.getStatus());

    // Give some time to process the connection.
    Thread.sleep(1000);

    // Connection should have been removed from pool.
    HttpDestinationOverHTTP destination =
        (HttpDestinationOverHTTP) client.getDestination("http", "localhost", port);
    DuplexConnectionPool connectionPool = (DuplexConnectionPool) destination.getConnectionPool();
    Assert.assertEquals(0, connectionPool.getConnectionCount());
    Assert.assertEquals(0, connectionPool.getIdleConnectionCount());
    Assert.assertEquals(0, connectionPool.getActiveConnectionCount());
  }