@Test
  public void testPartialReadThenClose() throws Exception {
    server.setHandler(new PartialReaderHandler());
    server.start();

    try (final Socket socket = new Socket("localhost", connector.getLocalPort())) {
      socket.setSoTimeout(1000);

      byte[] content = new byte[32 * 4096];
      Arrays.fill(content, (byte) 88);

      OutputStream out = socket.getOutputStream();
      String header =
          "POST /?read=10 HTTP/1.1\r\n"
              + "Host: localhost\r\n"
              + "Content-Length: "
              + content.length
              + "\r\n"
              + "Content-Type: bytes\r\n"
              + "\r\n";
      byte[] h = header.getBytes(StandardCharsets.ISO_8859_1);
      out.write(h);
      out.write(content, 0, 4096);
      out.flush();

      BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
      assertThat(in.readLine(), containsString("HTTP/1.1 200 OK"));
      assertThat(in.readLine(), containsString("Content-Length:"));
      assertThat(in.readLine(), containsString("Server:"));
      in.readLine();
      assertThat(in.readLine(), containsString("XXXXXXX"));

      socket.close();
    }
  }
  @Test
  public void testPipelined() throws Exception {
    server.setHandler(new AsyncStreamHandler());
    server.start();

    try (final Socket socket = new Socket("localhost", connector.getLocalPort())) {
      socket.setSoTimeout(1000);

      byte[] content = new byte[32 * 4096];
      Arrays.fill(content, (byte) 120);

      OutputStream out = socket.getOutputStream();
      String header =
          "POST / HTTP/1.1\r\n"
              + "Host: localhost\r\n"
              + "Content-Length: "
              + content.length
              + "\r\n"
              + "Content-Type: bytes\r\n"
              + "\r\n";
      byte[] h = header.getBytes(StandardCharsets.ISO_8859_1);
      out.write(h);
      out.write(content);

      header =
          "POST / HTTP/1.1\r\n"
              + "Host: localhost\r\n"
              + "Content-Length: "
              + content.length
              + "\r\n"
              + "Content-Type: bytes\r\n"
              + "Connection: close\r\n"
              + "\r\n";
      h = header.getBytes(StandardCharsets.ISO_8859_1);
      out.write(h);
      out.write(content);
      out.flush();

      InputStream in = socket.getInputStream();
      String response = IO.toString(in);
      assertTrue(response.indexOf("200 OK") > 0);

      long total = __total.poll(5, TimeUnit.SECONDS);
      assertEquals(content.length, total);
      total = __total.poll(5, TimeUnit.SECONDS);
      assertEquals(content.length, total);
    }
  }
  public void asyncReadTest(int contentSize, int chunkSize, int chunks, int delayMS)
      throws Exception {
    String tst = contentSize + "," + chunkSize + "," + chunks + "," + delayMS;
    // System.err.println(tst);

    try (final Socket socket = new Socket("localhost", connector.getLocalPort())) {

      byte[] content = new byte[contentSize];
      Arrays.fill(content, (byte) 120);

      OutputStream out = socket.getOutputStream();
      out.write("POST / HTTP/1.1\r\n".getBytes());
      out.write("Host: localhost\r\n".getBytes());
      out.write(("Content-Length: " + content.length + "\r\n").getBytes());
      out.write("Content-Type: bytes\r\n".getBytes());
      out.write("Connection: close\r\n".getBytes());
      out.write("\r\n".getBytes());
      out.flush();

      int offset = 0;
      for (int i = 0; i < chunks; i++) {
        out.write(content, offset, chunkSize);
        offset += chunkSize;
        Thread.sleep(delayMS);
      }
      out.write(content, offset, content.length - offset);

      out.flush();

      InputStream in = socket.getInputStream();
      String response = IO.toString(in);
      assertTrue(tst, response.indexOf("200 OK") > 0);

      long total = __total.poll(30, TimeUnit.SECONDS);
      assertEquals(tst, content.length, total);
    }
  }
  @Test
  public void testSlowClientWithPipelinedRequest() throws Exception {
    final int contentLength = 512 * 1024;
    startServer(
        new AbstractHandler() {
          @Override
          public void handle(
              String target,
              Request baseRequest,
              HttpServletRequest request,
              HttpServletResponse response)
              throws IOException, ServletException {
            baseRequest.setHandled(true);
            if ("/content".equals(target)) {
              // We simulate what the DefaultServlet does, bypassing the blocking
              // write mechanism otherwise the test does not reproduce the bug
              OutputStream outputStream = response.getOutputStream();
              HttpOutput output = (HttpOutput) outputStream;
              // Since the test is via localhost, we need a really big buffer to stall the write
              byte[] bytes = new byte[contentLength];
              Arrays.fill(bytes, (byte) '9');
              ByteBuffer buffer = ByteBuffer.wrap(bytes);
              // Do a non blocking write
              output.sendContent(buffer);
            }
          }
        });

    Socket client = new Socket("localhost", connector.getLocalPort());
    OutputStream output = client.getOutputStream();
    output.write(
        (""
                + "GET /content HTTP/1.1\r\n"
                + "Host: localhost:"
                + connector.getLocalPort()
                + "\r\n"
                + "\r\n"
                + "")
            .getBytes(StandardCharsets.UTF_8));
    output.flush();

    InputStream input = client.getInputStream();

    int read = input.read();
    Assert.assertTrue(read >= 0);
    // As soon as we can read the response, send a pipelined request
    // so it is a different read for the server and it will trigger NIO
    output.write(
        (""
                + "GET /pipelined HTTP/1.1\r\n"
                + "Host: localhost:"
                + connector.getLocalPort()
                + "\r\n"
                + "\r\n"
                + "")
            .getBytes(StandardCharsets.UTF_8));
    output.flush();

    // Simulate a slow reader
    Thread.sleep(1000);
    Assert.assertThat(handles.get(), lessThan(10));

    // We are sure we are not spinning, read the content
    StringBuilder lines = new StringBuilder().append((char) read);
    int crlfs = 0;
    while (true) {
      read = input.read();
      lines.append((char) read);
      if (read == '\r' || read == '\n') ++crlfs;
      else crlfs = 0;
      if (crlfs == 4) break;
    }
    Assert.assertTrue(lines.toString().contains(" 200 "));
    // Read the body
    for (int i = 0; i < contentLength; ++i) input.read();

    // Read the pipelined response
    lines.setLength(0);
    crlfs = 0;
    while (true) {
      read = input.read();
      lines.append((char) read);
      if (read == '\r' || read == '\n') ++crlfs;
      else crlfs = 0;
      if (crlfs == 4) break;
    }
    Assert.assertTrue(lines.toString().contains(" 200 "));

    client.close();
  }