public SpdyClientConnection(SpdyChannel spdyChannel, ClientStatistics clientStatistics) {
   this.spdyChannel = spdyChannel;
   this.clientStatistics = clientStatistics;
   spdyChannel.getReceiveSetter().set(new SpdyReceiveListener());
   spdyChannel.resumeReceives();
   spdyChannel.addCloseTask(
       new ChannelListener<SpdyChannel>() {
         @Override
         public void handleEvent(SpdyChannel channel) {
           ChannelListeners.invokeChannelListener(SpdyClientConnection.this, closeSetter.get());
         }
       });
 }
    @Override
    public void handleEvent(SpdyChannel channel) {
      try {
        SpdyStreamSourceChannel result = channel.receive();
        if (result instanceof SpdySynReplyStreamSourceChannel) {
          final int streamId = ((SpdySynReplyStreamSourceChannel) result).getStreamId();
          SpdyClientExchange request = currentExchanges.get(streamId);
          result.addCloseTask(
              new ChannelListener<SpdyStreamSourceChannel>() {
                @Override
                public void handleEvent(SpdyStreamSourceChannel channel) {
                  currentExchanges.remove(streamId);
                }
              });
          if (request == null) {

            // server side initiated stream, we can't deal with that at the moment
            // just fail
            // TODO: either handle this properly or at the very least send RST_STREAM
            channel.sendGoAway(SpdyChannel.CLOSE_PROTOCOL_ERROR);
            IoUtils.safeClose(SpdyClientConnection.this);
            return;
          }
          request.responseReady((SpdySynReplyStreamSourceChannel) result);

        } else if (result instanceof SpdyPingStreamSourceChannel) {
          handlePing((SpdyPingStreamSourceChannel) result);
        } else if (result instanceof SpdyRstStreamStreamSourceChannel) {
          int stream = ((SpdyRstStreamStreamSourceChannel) result).getStreamId();
          UndertowLogger.REQUEST_LOGGER.debugf("Client received RST_STREAM for stream %s", stream);
          SpdyClientExchange exchange = currentExchanges.get(stream);
          if (exchange != null) {
            exchange.failed(UndertowMessages.MESSAGES.spdyStreamWasReset());
          }
        } else if (!channel.isOpen()) {
          throw UndertowMessages.MESSAGES.channelIsClosed();
        }

      } catch (IOException e) {
        UndertowLogger.REQUEST_IO_LOGGER.ioException(e);
        IoUtils.safeClose(SpdyClientConnection.this);
        for (Map.Entry<Integer, SpdyClientExchange> entry : currentExchanges.entrySet()) {
          try {
            entry.getValue().failed(e);
          } catch (Exception ex) {
            UndertowLogger.REQUEST_IO_LOGGER.ioException(new IOException(ex));
          }
        }
      }
    }
  @Override
  public void sendRequest(ClientRequest request, ClientCallback<ClientExchange> clientCallback) {
    request.getRequestHeaders().put(PATH, request.getPath());
    request.getRequestHeaders().put(SCHEME, "https");
    request.getRequestHeaders().put(VERSION, request.getProtocol().toString());
    request.getRequestHeaders().put(METHOD, request.getMethod().toString());
    request.getRequestHeaders().put(HOST, request.getRequestHeaders().getFirst(Headers.HOST));
    request.getRequestHeaders().remove(Headers.HOST);

    SpdySynStreamStreamSinkChannel sinkChannel;
    try {
      sinkChannel = spdyChannel.createStream(request.getRequestHeaders());
    } catch (IOException e) {
      clientCallback.failed(e);
      return;
    }
    SpdyClientExchange exchange = new SpdyClientExchange(this, sinkChannel, request);
    currentExchanges.put(sinkChannel.getStreamId(), exchange);

    boolean hasContent = true;

    String fixedLengthString = request.getRequestHeaders().getFirst(CONTENT_LENGTH);
    String transferEncodingString = request.getRequestHeaders().getLast(TRANSFER_ENCODING);
    if (fixedLengthString != null) {
      try {
        long length = Long.parseLong(fixedLengthString);
        hasContent = length != 0;
      } catch (NumberFormatException e) {
        handleError(new IOException(e));
        return;
      }
    } else if (transferEncodingString == null) {
      hasContent = false;
    }
    if (clientCallback != null) {
      clientCallback.completed(exchange);
    }
    if (!hasContent) {
      // if there is no content we flush the response channel.
      // otherwise it is up to the user
      try {
        sinkChannel.shutdownWrites();
        if (!sinkChannel.flush()) {
          sinkChannel
              .getWriteSetter()
              .set(
                  ChannelListeners.flushingChannelListener(
                      null,
                      new ChannelExceptionHandler<StreamSinkChannel>() {
                        @Override
                        public void handleException(
                            StreamSinkChannel channel, IOException exception) {
                          handleError(exception);
                        }
                      }));
          sinkChannel.resumeWrites();
        }
      } catch (IOException e) {
        handleError(e);
      }
    } else if (!sinkChannel.isWriteResumed()) {
      try {
        // TODO: this needs some more thought
        if (!sinkChannel.flush()) {
          sinkChannel
              .getWriteSetter()
              .set(
                  new ChannelListener<StreamSinkChannel>() {
                    @Override
                    public void handleEvent(StreamSinkChannel channel) {
                      try {
                        if (channel.flush()) {
                          channel.suspendWrites();
                        }
                      } catch (IOException e) {
                        handleError(e);
                      }
                    }
                  });
          sinkChannel.resumeWrites();
        }
      } catch (IOException e) {
        handleError(e);
      }
    }
  }
 @Override
 public boolean isOpen() {
   return spdyChannel.isOpen();
 }
 @Override
 public void close() throws IOException {
   spdyChannel.sendGoAway(SpdyChannel.CLOSE_OK);
 }
 @Override
 public XnioIoThread getIoThread() {
   return spdyChannel.getIoThread();
 }
 @Override
 public XnioWorker getWorker() {
   return spdyChannel.getWorker();
 }
 @Override
 public <A extends SocketAddress> A getLocalAddress(Class<A> type) {
   return spdyChannel.getLocalAddress(type);
 }
 @Override
 public SocketAddress getLocalAddress() {
   return spdyChannel.getLocalAddress();
 }
 @Override
 public Pool<ByteBuffer> getBufferPool() {
   return spdyChannel.getBufferPool();
 }