@Test
  public void testOpenedClosedAreInvoked() throws Exception {
    initConnector(null);

    final CountDownLatch openedLatch = new CountDownLatch(1);
    final CountDownLatch closedLatch = new CountDownLatch(1);
    connector.addNetworkTrafficListener(
        new NetworkTrafficListener.Adapter() {
          public volatile Socket socket;

          @Override
          public void opened(Socket socket) {
            this.socket = socket;
            openedLatch.countDown();
          }

          @Override
          public void closed(Socket socket) {
            if (this.socket == socket) closedLatch.countDown();
          }
        });
    int port = connector.getLocalPort();

    // Connect to the server
    Socket socket = new Socket("localhost", port);
    assertTrue(openedLatch.await(10, TimeUnit.SECONDS));

    socket.close();
    assertTrue(closedLatch.await(10, TimeUnit.SECONDS));
  }
  public void initConnector(Handler handler) throws Exception {
    server = new Server();

    connector = new NetworkTrafficServerConnector(server);
    connector
        .getConnectionFactory(HttpConfiguration.ConnectionFactory.class)
        .getHttpConfiguration()
        .setSendDateHeader(false);
    connector
        .getConnectionFactory(HttpConfiguration.ConnectionFactory.class)
        .getHttpConfiguration()
        .setSendServerVersion(false);
    server.addConnector(connector);
    server.setHandler(handler);
    server.start();
  }
  @Test
  public void testTrafficWithRequestContentWithResponseRedirectOnPersistentConnection()
      throws Exception {
    final String location = "/redirect";
    initConnector(
        new AbstractHandler() {
          public void handle(
              String uri,
              Request request,
              HttpServletRequest servletRequest,
              HttpServletResponse servletResponse)
              throws IOException, ServletException {
            request.setHandled(true);
            servletResponse.sendRedirect(location);
          }
        });

    final AtomicReference<String> incomingData = new AtomicReference<>();
    final CountDownLatch incomingLatch = new CountDownLatch(1);
    final AtomicReference<String> outgoingData = new AtomicReference<>("");
    final CountDownLatch outgoingLatch = new CountDownLatch(1);
    connector.addNetworkTrafficListener(
        new NetworkTrafficListener.Adapter() {
          public void incoming(Socket socket, ByteBuffer bytes) {
            incomingData.set(BufferUtil.toString(bytes, StandardCharsets.UTF_8));
            incomingLatch.countDown();
          }

          public void outgoing(Socket socket, ByteBuffer bytes) {
            outgoingData.set(
                outgoingData.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
            outgoingLatch.countDown();
          }
        });
    int port = connector.getLocalPort();

    String requestContent = "a=1&b=2";
    String request =
        ""
            + "POST / HTTP/1.1\r\n"
            + "Host: localhost:"
            + port
            + "\r\n"
            + "Content-Type: application/x-www-form-urlencoded\r\n"
            + "Content-Length: "
            + requestContent.length()
            + "\r\n"
            + "\r\n"
            + requestContent;
    String expectedResponse =
        ""
            + "HTTP/1.1 302 Found\r\n"
            + "Location: http://localhost:"
            + port
            + location
            + "\r\n"
            + "Content-Length: 0\r\n"
            + "\r\n";

    Socket socket = new Socket("localhost", port);
    OutputStream output = socket.getOutputStream();
    output.write(request.getBytes(StandardCharsets.UTF_8));
    output.flush();

    assertTrue(incomingLatch.await(1, TimeUnit.SECONDS));
    assertEquals(request, incomingData.get());

    assertTrue(outgoingLatch.await(1, TimeUnit.SECONDS));
    assertEquals(expectedResponse, outgoingData.get());

    byte[] responseBytes = readResponse(socket);
    String response = new String(responseBytes, StandardCharsets.UTF_8);
    assertEquals(expectedResponse, response);

    socket.close();
  }
  @Test
  public void testTrafficWithBigRequestContentOnPersistentConnection() throws Exception {
    initConnector(
        new AbstractHandler() {
          public void handle(
              String uri,
              Request request,
              HttpServletRequest servletRequest,
              HttpServletResponse servletResponse)
              throws IOException, ServletException {
            // Read and discard the request body to make the test more
            // reliable, otherwise there is a race between request body
            // upload and response download
            InputStream input = servletRequest.getInputStream();
            byte[] buffer = new byte[4096];
            while (true) {
              int read = input.read(buffer);
              if (read < 0) break;
            }
            request.setHandled(true);
          }
        });

    final AtomicReference<String> incomingData = new AtomicReference<>("");
    final AtomicReference<String> outgoingData = new AtomicReference<>("");
    final CountDownLatch outgoingLatch = new CountDownLatch(1);
    connector.addNetworkTrafficListener(
        new NetworkTrafficListener.Adapter() {
          public void incoming(Socket socket, ByteBuffer bytes) {
            incomingData.set(
                incomingData.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
          }

          public void outgoing(Socket socket, ByteBuffer bytes) {
            outgoingData.set(
                outgoingData.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
            outgoingLatch.countDown();
          }
        });
    int port = connector.getLocalPort();

    // Generate 32 KiB of request content
    String requestContent = "0123456789ABCDEF";
    for (int i = 0; i < 11; ++i) requestContent += requestContent;
    String request =
        ""
            + "POST / HTTP/1.1\r\n"
            + "Host: localhost:"
            + port
            + "\r\n"
            + "Content-Type: text/plain\r\n"
            + "Content-Length: "
            + requestContent.length()
            + "\r\n"
            + "\r\n"
            + requestContent;
    String expectedResponse = "" + "HTTP/1.1 200 OK\r\n" + "Content-Length: 0\r\n" + "\r\n";

    Socket socket = new Socket("localhost", port);
    OutputStream output = socket.getOutputStream();
    output.write(request.getBytes(StandardCharsets.UTF_8));
    output.flush();

    assertTrue(outgoingLatch.await(1, TimeUnit.SECONDS));
    assertEquals(expectedResponse, outgoingData.get());

    byte[] responseBytes = readResponse(socket);
    String response = new String(responseBytes, StandardCharsets.UTF_8);
    assertEquals(expectedResponse, response);

    assertEquals(request, incomingData.get());

    socket.close();
  }
  @Test
  public void testTrafficWithResponseContentChunkedOnPersistentConnection() throws Exception {
    final String responseContent = "response_content";
    final String responseChunk1 = "response_content".substring(0, responseContent.length() / 2);
    final String responseChunk2 =
        "response_content".substring(responseContent.length() / 2, responseContent.length());
    initConnector(
        new AbstractHandler() {
          public void handle(
              String uri,
              Request request,
              HttpServletRequest servletRequest,
              HttpServletResponse servletResponse)
              throws IOException, ServletException {
            request.setHandled(true);
            ServletOutputStream output = servletResponse.getOutputStream();
            output.write(responseChunk1.getBytes(StandardCharsets.UTF_8));
            output.flush();
            output.write(responseChunk2.getBytes(StandardCharsets.UTF_8));
            output.flush();
          }
        });

    final AtomicReference<String> incomingData = new AtomicReference<>();
    final CountDownLatch incomingLatch = new CountDownLatch(1);
    final AtomicReference<String> outgoingData = new AtomicReference<>("");
    final CountDownLatch outgoingLatch = new CountDownLatch(4);
    connector.addNetworkTrafficListener(
        new NetworkTrafficListener.Adapter() {
          public void incoming(Socket socket, ByteBuffer bytes) {
            incomingData.set(BufferUtil.toString(bytes, StandardCharsets.UTF_8));
            incomingLatch.countDown();
          }

          public void outgoing(Socket socket, ByteBuffer bytes) {
            outgoingData.set(
                outgoingData.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
            outgoingLatch.countDown();
          }
        });
    int port = connector.getLocalPort();

    String request = "" + "GET / HTTP/1.1\r\n" + "Host: localhost:" + port + "\r\n" + "\r\n";
    String expectedResponse =
        ""
            + "HTTP/1.1 200 OK\r\n"
            + "Transfer-Encoding: chunked\r\n"
            + "\r\n"
            + responseChunk1.length()
            + "\r\n"
            + responseChunk1
            + "\r\n"
            + responseChunk2.length()
            + "\r\n"
            + responseChunk2
            + "\r\n"
            + "0\r\n"
            + "\r\n";

    Socket socket = new Socket("localhost", port);
    OutputStream output = socket.getOutputStream();
    output.write(request.getBytes(StandardCharsets.UTF_8));
    output.flush();

    assertTrue(incomingLatch.await(1, TimeUnit.SECONDS));
    assertEquals(request, incomingData.get());

    assertTrue(outgoingLatch.await(1, TimeUnit.SECONDS));
    assertEquals(expectedResponse, outgoingData.get());

    byte[] responseBytes = readResponse(socket);
    String response = new String(responseBytes, StandardCharsets.UTF_8);
    assertEquals(expectedResponse, response);

    socket.close();
  }