@Override
 public boolean parsedHeader(HttpField field) {
   if (field.getHeader() == HttpHeader.HOST)
     headers.put(HTTPSPDYHeader.HOST.name(version), field.getValue());
   else headers.put(field.getName(), field.getValue());
   return false;
 }
  @Test
  public void testSYNThenREPLYAndDATA() throws Exception {
    final byte[] data = "0123456789ABCDEF".getBytes("UTF-8");
    final String header = "foo";
    InetSocketAddress proxyAddress =
        startProxy(
            startServer(
                new ServerSessionFrameListener.Adapter() {
                  @Override
                  public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) {
                    Fields requestHeaders = synInfo.getHeaders();
                    Assert.assertNotNull(requestHeaders.get("via"));
                    Assert.assertNotNull(requestHeaders.get(header));

                    Fields responseHeaders = new Fields();
                    responseHeaders.put(header, "baz");
                    stream.reply(new ReplyInfo(responseHeaders, false), new Callback.Adapter());
                    stream.data(new BytesDataInfo(data, true), new Callback.Adapter());
                    return null;
                  }
                }));
    proxyConnector.addConnectionFactory(proxyConnector.getConnectionFactory("spdy/" + version));

    Session client =
        factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);

    final CountDownLatch replyLatch = new CountDownLatch(1);
    final CountDownLatch dataLatch = new CountDownLatch(1);
    Fields headers = new Fields();
    headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + proxyAddress.getPort());
    headers.put(header, "bar");
    client.syn(
        new SynInfo(headers, true),
        new StreamFrameListener.Adapter() {
          private final ByteArrayOutputStream result = new ByteArrayOutputStream();

          @Override
          public void onReply(Stream stream, ReplyInfo replyInfo) {
            Fields headers = replyInfo.getHeaders();
            Assert.assertNotNull(headers.get(header));
            replyLatch.countDown();
          }

          @Override
          public void onData(Stream stream, DataInfo dataInfo) {
            result.write(dataInfo.asBytes(true), 0, dataInfo.length());
            if (dataInfo.isClose()) {
              Assert.assertArrayEquals(data, result.toByteArray());
              dataLatch.countDown();
            }
          }
        });

    Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
    Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));

    client.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
  }
  @Test
  public void testSYNThenReset() throws Exception {
    InetSocketAddress proxyAddress =
        startProxy(
            startServer(
                new ServerSessionFrameListener.Adapter() {
                  @Override
                  public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) {
                    Assert.assertTrue(synInfo.isClose());
                    Fields requestHeaders = synInfo.getHeaders();
                    Assert.assertNotNull(requestHeaders.get("via"));

                    stream
                        .getSession()
                        .rst(
                            new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM),
                            new Callback.Adapter());

                    return null;
                  }
                }));
    proxyConnector.addConnectionFactory(proxyConnector.getConnectionFactory("spdy/" + version));

    final CountDownLatch resetLatch = new CountDownLatch(1);
    Session client =
        factory
            .newSPDYClient(version)
            .connect(
                proxyAddress,
                new SessionFrameListener.Adapter() {
                  @Override
                  public void onRst(Session session, RstInfo rstInfo) {
                    resetLatch.countDown();
                  }
                })
            .get(5, TimeUnit.SECONDS);

    Fields headers = new Fields();
    headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + proxyAddress.getPort());
    client.syn(new SynInfo(headers, true), null);

    Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS));

    client.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
  }
  @Test
  public void testSYNThenREPLY() throws Exception {
    final String header = "foo";
    InetSocketAddress proxyAddress =
        startProxy(
            startServer(
                new ServerSessionFrameListener.Adapter() {
                  @Override
                  public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) {
                    Fields requestHeaders = synInfo.getHeaders();
                    Assert.assertNotNull(requestHeaders.get("via"));
                    Assert.assertNotNull(requestHeaders.get(header));

                    Fields responseHeaders = new Fields();
                    responseHeaders.put(header, "baz");
                    stream.reply(new ReplyInfo(responseHeaders, true), new Callback.Adapter());
                    return null;
                  }
                }));
    proxyConnector.addConnectionFactory(proxyConnector.getConnectionFactory("spdy/" + version));

    Session client =
        factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);

    final CountDownLatch replyLatch = new CountDownLatch(1);
    Fields headers = new Fields();
    headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + proxyAddress.getPort());
    headers.put(header, "bar");
    client.syn(
        new SynInfo(headers, true),
        new StreamFrameListener.Adapter() {
          @Override
          public void onReply(Stream stream, ReplyInfo replyInfo) {
            Fields headers = replyInfo.getHeaders();
            Assert.assertNotNull(headers.get(header));
            replyLatch.countDown();
          }
        });

    Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));

    client.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
  }
    @Override
    public void reply(ReplyInfo replyInfo, Callback handler) {
      try {
        Fields headers = new Fields(replyInfo.getHeaders(), false);

        headers.remove(HTTPSPDYHeader.SCHEME.name(version));

        String status = headers.remove(HTTPSPDYHeader.STATUS.name(version)).value();
        Matcher matcher = statusRegexp.matcher(status);
        matcher.matches();
        int code = Integer.parseInt(matcher.group(1));
        String reason = matcher.group(2).trim();

        HttpVersion httpVersion =
            HttpVersion.fromString(headers.remove(HTTPSPDYHeader.VERSION.name(version)).value());

        // Convert the Host header from a SPDY special header to a normal header
        Fields.Field host = headers.remove(HTTPSPDYHeader.HOST.name(version));
        if (host != null) headers.put("host", host.value());

        HttpFields fields = new HttpFields();
        for (Fields.Field header : headers) {
          String name = camelize(header.name());
          fields.put(name, header.value());
        }

        // TODO: handle better the HEAD last parameter
        HttpGenerator.ResponseInfo info =
            new HttpGenerator.ResponseInfo(httpVersion, fields, -1, code, reason, false);
        send(info, null, replyInfo.isClose());

        if (replyInfo.isClose()) completed();

        handler.succeeded();
      } catch (IOException x) {
        handler.failed(x);
      }
    }
  @Test
  public void testSYNThenSPDYNestedPushIsReceived() throws Exception {
    final byte[] data = "0123456789ABCDEF".getBytes("UTF-8");
    InetSocketAddress proxyAddress =
        startProxy(
            startServer(
                new ServerSessionFrameListener.Adapter() {
                  @Override
                  public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) {
                    Fields responseHeaders = new Fields();
                    responseHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
                    responseHeaders.put(HTTPSPDYHeader.STATUS.name(version), "200 OK");
                    stream.reply(new ReplyInfo(responseHeaders, false), new Callback.Adapter());

                    final Fields pushHeaders = new Fields();
                    pushHeaders.put(HTTPSPDYHeader.URI.name(version), "/push");
                    stream.push(
                        new PushInfo(5, TimeUnit.SECONDS, pushHeaders, false),
                        new Promise.Adapter<Stream>() {
                          @Override
                          public void succeeded(Stream pushStream) {
                            pushHeaders.put(HTTPSPDYHeader.URI.name(version), "/nestedpush");
                            pushStream.push(
                                new PushInfo(5, TimeUnit.SECONDS, pushHeaders, false),
                                new Adapter<Stream>() {
                                  @Override
                                  public void succeeded(Stream pushStream) {
                                    pushHeaders.put(
                                        HTTPSPDYHeader.URI.name(version), "/anothernestedpush");
                                    pushStream.push(
                                        new PushInfo(5, TimeUnit.SECONDS, pushHeaders, false),
                                        new Adapter<Stream>() {
                                          @Override
                                          public void succeeded(Stream pushStream) {
                                            pushStream.data(
                                                new BytesDataInfo(data, true),
                                                new Callback.Adapter());
                                          }
                                        });
                                    pushStream.data(
                                        new BytesDataInfo(data, true), new Callback.Adapter());
                                  }
                                });
                            pushStream.data(new BytesDataInfo(data, true), new Callback.Adapter());
                          }
                        });

                    stream.data(new BytesDataInfo(data, true), new Callback.Adapter());

                    return null;
                  }
                }));
    proxyConnector.addConnectionFactory(proxyConnector.getConnectionFactory("spdy/" + version));

    final CountDownLatch pushSynLatch = new CountDownLatch(3);
    final CountDownLatch pushDataLatch = new CountDownLatch(3);
    Session client =
        factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);

    Fields headers = new Fields();
    headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + proxyAddress.getPort());
    final CountDownLatch replyLatch = new CountDownLatch(1);
    final CountDownLatch dataLatch = new CountDownLatch(1);
    client.syn(
        new SynInfo(headers, true),
        new StreamFrameListener.Adapter() {
          // onPush for 1st push stream
          @Override
          public StreamFrameListener onPush(Stream stream, PushInfo pushInfo) {
            pushSynLatch.countDown();
            return new StreamFrameListener.Adapter() {
              // onPush for 2nd nested push stream
              @Override
              public StreamFrameListener onPush(Stream stream, PushInfo pushInfo) {
                pushSynLatch.countDown();
                return new Adapter() {
                  // onPush for 3rd nested push stream
                  @Override
                  public StreamFrameListener onPush(Stream stream, PushInfo pushInfo) {
                    pushSynLatch.countDown();
                    return new Adapter() {
                      @Override
                      public void onData(Stream stream, DataInfo dataInfo) {
                        dataInfo.consume(dataInfo.length());
                        if (dataInfo.isClose()) pushDataLatch.countDown();
                      }
                    };
                  }

                  @Override
                  public void onData(Stream stream, DataInfo dataInfo) {
                    dataInfo.consume(dataInfo.length());
                    if (dataInfo.isClose()) pushDataLatch.countDown();
                  }
                };
              }

              @Override
              public void onData(Stream stream, DataInfo dataInfo) {
                dataInfo.consume(dataInfo.length());
                if (dataInfo.isClose()) pushDataLatch.countDown();
              }
            };
          }

          @Override
          public void onReply(Stream stream, ReplyInfo replyInfo) {
            replyLatch.countDown();
          }

          @Override
          public void onData(Stream stream, DataInfo dataInfo) {
            dataInfo.consume(dataInfo.length());
            if (dataInfo.isClose()) dataLatch.countDown();
          }
        });

    Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
    Assert.assertTrue(pushSynLatch.await(5, TimeUnit.SECONDS));
    Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS));
    Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));

    client.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
  }