private static void addHttpHeaders(ClientRequest request, HttpHeaders headers) {
   HeaderMap headerMap = request.getRequestHeaders();
   for (String name : headers.keySet()) {
     for (String value : headers.get(name)) {
       headerMap.add(HttpString.tryFromString(name), value);
     }
   }
 }
  protected ResponseEntity<String> executeRequest(
      URI url, HttpString method, HttpHeaders headers, String body) {
    CountDownLatch latch = new CountDownLatch(1);
    List<ClientResponse> responses = new CopyOnWriteArrayList<ClientResponse>();

    try {
      ClientConnection connection =
          this.undertowBufferSupport
              .httpClientConnect(this.httpClient, url, this.worker, this.optionMap)
              .get();
      try {
        ClientRequest request = new ClientRequest().setMethod(method).setPath(url.getPath());
        request.getRequestHeaders().add(HttpString.tryFromString(HttpHeaders.HOST), url.getHost());
        if (body != null && !body.isEmpty()) {
          HttpString headerName = HttpString.tryFromString(HttpHeaders.CONTENT_LENGTH);
          request.getRequestHeaders().add(headerName, body.length());
        }
        addHttpHeaders(request, headers);
        connection.sendRequest(request, createRequestCallback(body, responses, latch));

        latch.await();
        ClientResponse response = responses.iterator().next();
        HttpStatus status = HttpStatus.valueOf(response.getResponseCode());
        HttpHeaders responseHeaders = toHttpHeaders(response.getResponseHeaders());
        String responseBody = response.getAttachment(RESPONSE_BODY);
        return (responseBody != null
            ? new ResponseEntity<String>(responseBody, responseHeaders, status)
            : new ResponseEntity<String>(responseHeaders, status));
      } finally {
        IoUtils.safeClose(connection);
      }
    } catch (IOException ex) {
      throw new SockJsTransportFailureException("Failed to execute request to " + url, ex);
    } catch (InterruptedException ex) {
      throw new SockJsTransportFailureException(
          "Interrupted while processing request to " + url, ex);
    }
  }
 /**
  * Handles writing out the header data. It can also take a byte buffer of user data, to enable
  * both user data and headers to be written out in a single operation, which has a noticeable
  * performance impact.
  *
  * <p>It is up to the caller to note the current position of this buffer before and after they
  * call this method, and use this to figure out how many bytes (if any) have been written.
  *
  * @param state
  * @param userData
  * @return
  * @throws java.io.IOException
  */
 private int processWrite(int state, final ByteBuffer userData) throws IOException {
   if (state == STATE_START) {
     pooledBuffer = pool.allocate();
   }
   ClientRequest request = this.request;
   ByteBuffer buffer = pooledBuffer.getResource();
   Iterator<HttpString> nameIterator = this.nameIterator;
   Iterator<String> valueIterator = this.valueIterator;
   int charIndex = this.charIndex;
   int length;
   String string = this.string;
   HttpString headerName = this.headerName;
   int res;
   // BUFFER IS FLIPPED COMING IN
   if (state != STATE_START && buffer.hasRemaining()) {
     log.trace("Flushing remaining buffer");
     do {
       res = next.write(buffer);
       if (res == 0) {
         return state;
       }
     } while (buffer.hasRemaining());
   }
   buffer.clear();
   // BUFFER IS NOW EMPTY FOR FILLING
   for (; ; ) {
     switch (state) {
       case STATE_BODY:
         {
           // shouldn't be possible, but might as well do the right thing anyway
           return state;
         }
       case STATE_START:
         {
           log.trace("Starting request");
           // we assume that our buffer has enough space for the initial request line plus one more
           // CR+LF
           assert buffer.remaining() >= 50;
           request.getMethod().appendTo(buffer);
           buffer.put((byte) ' ');
           string = request.getPath();
           length = string.length();
           for (charIndex = 0; charIndex < length; charIndex++) {
             buffer.put((byte) string.charAt(charIndex));
           }
           buffer.put((byte) ' ');
           request.getProtocol().appendTo(buffer);
           buffer.put((byte) '\r').put((byte) '\n');
           HeaderMap headers = request.getRequestHeaders();
           nameIterator = headers.getHeaderNames().iterator();
           if (!nameIterator.hasNext()) {
             log.trace("No request headers");
             buffer.put((byte) '\r').put((byte) '\n');
             buffer.flip();
             while (buffer.hasRemaining()) {
               res = next.write(buffer);
               if (res == 0) {
                 log.trace("Continuation");
                 return STATE_BUF_FLUSH;
               }
             }
             pooledBuffer.free();
             pooledBuffer = null;
             log.trace("Body");
             return STATE_BODY;
           }
           headerName = nameIterator.next();
           charIndex = 0;
           // fall thru
         }
       case STATE_HDR_NAME:
         {
           log.tracef("Processing header '%s'", headerName);
           length = headerName.length();
           while (charIndex < length) {
             if (buffer.hasRemaining()) {
               buffer.put(headerName.byteAt(charIndex++));
             } else {
               log.trace("Buffer flush");
               buffer.flip();
               do {
                 res = next.write(buffer);
                 if (res == 0) {
                   this.string = string;
                   this.headerName = headerName;
                   this.charIndex = charIndex;
                   this.valueIterator = valueIterator;
                   this.nameIterator = nameIterator;
                   log.trace("Continuation");
                   return STATE_HDR_NAME;
                 }
               } while (buffer.hasRemaining());
               buffer.clear();
             }
           }
           // fall thru
         }
       case STATE_HDR_D:
         {
           if (!buffer.hasRemaining()) {
             buffer.flip();
             do {
               res = next.write(buffer);
               if (res == 0) {
                 log.trace("Continuation");
                 this.string = string;
                 this.headerName = headerName;
                 this.charIndex = charIndex;
                 this.valueIterator = valueIterator;
                 this.nameIterator = nameIterator;
                 return STATE_HDR_D;
               }
             } while (buffer.hasRemaining());
             buffer.clear();
           }
           buffer.put((byte) ':');
           // fall thru
         }
       case STATE_HDR_DS:
         {
           if (!buffer.hasRemaining()) {
             buffer.flip();
             do {
               res = next.write(buffer);
               if (res == 0) {
                 log.trace("Continuation");
                 this.string = string;
                 this.headerName = headerName;
                 this.charIndex = charIndex;
                 this.valueIterator = valueIterator;
                 this.nameIterator = nameIterator;
                 return STATE_HDR_DS;
               }
             } while (buffer.hasRemaining());
             buffer.clear();
           }
           buffer.put((byte) ' ');
           if (valueIterator == null) {
             valueIterator = request.getRequestHeaders().get(headerName).iterator();
           }
           assert valueIterator.hasNext();
           string = valueIterator.next();
           charIndex = 0;
           // fall thru
         }
       case STATE_HDR_VAL:
         {
           log.tracef("Processing header value '%s'", string);
           length = string.length();
           while (charIndex < length) {
             if (buffer.hasRemaining()) {
               buffer.put((byte) string.charAt(charIndex++));
             } else {
               buffer.flip();
               do {
                 res = next.write(buffer);
                 if (res == 0) {
                   this.string = string;
                   this.headerName = headerName;
                   this.charIndex = charIndex;
                   this.valueIterator = valueIterator;
                   this.nameIterator = nameIterator;
                   log.trace("Continuation");
                   return STATE_HDR_VAL;
                 }
               } while (buffer.hasRemaining());
               buffer.clear();
             }
           }
           charIndex = 0;
           if (!valueIterator.hasNext()) {
             if (!buffer.hasRemaining()) {
               buffer.flip();
               do {
                 res = next.write(buffer);
                 if (res == 0) {
                   log.trace("Continuation");
                   return STATE_HDR_EOL_CR;
                 }
               } while (buffer.hasRemaining());
               buffer.clear();
             }
             buffer.put((byte) 13); // CR
             if (!buffer.hasRemaining()) {
               buffer.flip();
               do {
                 res = next.write(buffer);
                 if (res == 0) {
                   log.trace("Continuation");
                   return STATE_HDR_EOL_LF;
                 }
               } while (buffer.hasRemaining());
               buffer.clear();
             }
             buffer.put((byte) 10); // LF
             if (nameIterator.hasNext()) {
               headerName = nameIterator.next();
               valueIterator = null;
               state = STATE_HDR_NAME;
               break;
             } else {
               if (!buffer.hasRemaining()) {
                 buffer.flip();
                 do {
                   res = next.write(buffer);
                   if (res == 0) {
                     log.trace("Continuation");
                     return STATE_HDR_FINAL_CR;
                   }
                 } while (buffer.hasRemaining());
                 buffer.clear();
               }
               buffer.put((byte) 13); // CR
               if (!buffer.hasRemaining()) {
                 buffer.flip();
                 do {
                   res = next.write(buffer);
                   if (res == 0) {
                     log.trace("Continuation");
                     return STATE_HDR_FINAL_LF;
                   }
                 } while (buffer.hasRemaining());
                 buffer.clear();
               }
               buffer.put((byte) 10); // LF
               this.nameIterator = null;
               this.valueIterator = null;
               this.string = null;
               buffer.flip();
               // for performance reasons we use a gather write if there is user data
               if (userData == null) {
                 do {
                   res = next.write(buffer);
                   if (res == 0) {
                     log.trace("Continuation");
                     return STATE_BUF_FLUSH;
                   }
                 } while (buffer.hasRemaining());
               } else {
                 ByteBuffer[] b = {buffer, userData};
                 do {
                   long r = next.write(b, 0, b.length);
                   if (r == 0 && buffer.hasRemaining()) {
                     log.trace("Continuation");
                     return STATE_BUF_FLUSH;
                   }
                 } while (buffer.hasRemaining());
               }
               pooledBuffer.free();
               pooledBuffer = null;
               log.trace("Body");
               return STATE_BODY;
             }
             // not reached
           }
           // fall thru
         }
         // Clean-up states
       case STATE_HDR_EOL_CR:
         {
           if (!buffer.hasRemaining()) {
             buffer.flip();
             do {
               res = next.write(buffer);
               if (res == 0) {
                 log.trace("Continuation");
                 return STATE_HDR_EOL_CR;
               }
             } while (buffer.hasRemaining());
             buffer.clear();
           }
           buffer.put((byte) 13); // CR
         }
       case STATE_HDR_EOL_LF:
         {
           if (!buffer.hasRemaining()) {
             buffer.flip();
             do {
               res = next.write(buffer);
               if (res == 0) {
                 log.trace("Continuation");
                 return STATE_HDR_EOL_LF;
               }
             } while (buffer.hasRemaining());
             buffer.clear();
           }
           buffer.put((byte) 10); // LF
           if (valueIterator.hasNext()) {
             state = STATE_HDR_NAME;
             break;
           } else if (nameIterator.hasNext()) {
             headerName = nameIterator.next();
             valueIterator = null;
             state = STATE_HDR_NAME;
             break;
           }
           // fall thru
         }
       case STATE_HDR_FINAL_CR:
         {
           if (!buffer.hasRemaining()) {
             buffer.flip();
             do {
               res = next.write(buffer);
               if (res == 0) {
                 log.trace("Continuation");
                 return STATE_HDR_FINAL_CR;
               }
             } while (buffer.hasRemaining());
             buffer.clear();
           }
           buffer.put((byte) 13); // CR
           // fall thru
         }
       case STATE_HDR_FINAL_LF:
         {
           if (!buffer.hasRemaining()) {
             buffer.flip();
             do {
               res = next.write(buffer);
               if (res == 0) {
                 log.trace("Continuation");
                 return STATE_HDR_FINAL_LF;
               }
             } while (buffer.hasRemaining());
             buffer.clear();
           }
           buffer.put((byte) 10); // LF
           this.nameIterator = null;
           this.valueIterator = null;
           this.string = null;
           buffer.flip();
           // for performance reasons we use a gather write if there is user data
           if (userData == null) {
             do {
               res = next.write(buffer);
               if (res == 0) {
                 log.trace("Continuation");
                 return STATE_BUF_FLUSH;
               }
             } while (buffer.hasRemaining());
           } else {
             ByteBuffer[] b = {buffer, userData};
             do {
               long r = next.write(b, 0, b.length);
               if (r == 0 && buffer.hasRemaining()) {
                 log.trace("Continuation");
                 return STATE_BUF_FLUSH;
               }
             } while (buffer.hasRemaining());
           }
           // fall thru
         }
       case STATE_BUF_FLUSH:
         {
           // buffer was successfully flushed above
           pooledBuffer.free();
           pooledBuffer = null;
           return STATE_BODY;
         }
       default:
         {
           throw new IllegalStateException();
         }
     }
   }
 }
Example #4
0
    @Override
    public void run() {
      final ClientRequest request = new ClientRequest();

      StringBuilder requestURI = new StringBuilder();
      if (!clientConnection.getTargetPath().isEmpty()
          && !clientConnection.getTargetPath().equals("/")) {
        requestURI.append(clientConnection.getTargetPath());
      }

      if (exchange.isHostIncludedInRequestURI()) {
        int uriPart = exchange.getRequestURI().indexOf("//");
        if (uriPart == -1) {
          requestURI.append(exchange.getRequestURI());
        } else {
          uriPart = exchange.getRequestURI().indexOf("/", uriPart);
          requestURI.append(exchange.getRequestURI().substring(uriPart));
        }
      } else {
        requestURI.append(exchange.getRequestURI());
      }
      String qs = exchange.getQueryString();
      if (qs != null && !qs.isEmpty()) {
        requestURI.append('?');
        requestURI.append(qs);
      }
      request.setPath(requestURI.toString()).setMethod(exchange.getRequestMethod());
      final HeaderMap inboundRequestHeaders = exchange.getRequestHeaders();
      final HeaderMap outboundRequestHeaders = request.getRequestHeaders();
      copyHeaders(outboundRequestHeaders, inboundRequestHeaders);

      if (!exchange.isPersistent()) {
        // just because the client side is non-persistent
        // we don't want to close the connection to the backend
        outboundRequestHeaders.put(Headers.CONNECTION, "keep-alive");
      }
      if ("h2c".equals(exchange.getRequestHeaders().getFirst(Headers.UPGRADE))) {
        // we don't allow h2c upgrade requests to be passed through to the backend
        exchange.getRequestHeaders().remove(Headers.UPGRADE);
        outboundRequestHeaders.put(Headers.CONNECTION, "keep-alive");
      }

      for (Map.Entry<HttpString, ExchangeAttribute> entry : requestHeaders.entrySet()) {
        String headerValue = entry.getValue().readAttribute(exchange);
        if (headerValue == null || headerValue.isEmpty()) {
          outboundRequestHeaders.remove(entry.getKey());
        } else {
          outboundRequestHeaders.put(entry.getKey(), headerValue.replace('\n', ' '));
        }
      }

      final SocketAddress address = exchange.getConnection().getPeerAddress();
      final String remoteHost =
          (address != null && address instanceof InetSocketAddress)
              ? ((InetSocketAddress) address).getHostString()
              : "localhost";
      request.putAttachment(ProxiedRequestAttachments.REMOTE_HOST, remoteHost);

      if (reuseXForwarded && request.getRequestHeaders().contains(Headers.X_FORWARDED_FOR)) {
        // We have an existing header so we shall simply append the host to the existing list
        final String current = request.getRequestHeaders().getFirst(Headers.X_FORWARDED_FOR);
        if (current == null || current.isEmpty()) {
          // It was empty so just add it
          request.getRequestHeaders().put(Headers.X_FORWARDED_FOR, remoteHost);
        } else {
          // Add the new entry and reset the existing header
          request.getRequestHeaders().put(Headers.X_FORWARDED_FOR, current + "," + remoteHost);
        }
      } else {
        // No existing header or not allowed to reuse the header so set it here
        request.getRequestHeaders().put(Headers.X_FORWARDED_FOR, remoteHost);
      }

      // if we don't support push set a header saying so
      // this is non standard, and a problem with the HTTP2 spec, but they did not want to listen
      if (!exchange.getConnection().isPushSupported()
          && clientConnection.getConnection().isPushSupported()) {
        request.getRequestHeaders().put(Headers.X_DISABLE_PUSH, "true");
      }

      // Set the protocol header and attachment
      if (reuseXForwarded && exchange.getRequestHeaders().contains(Headers.X_FORWARDED_PROTO)) {
        final String proto = exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_PROTO);
        request.putAttachment(ProxiedRequestAttachments.IS_SSL, proto.equals("https"));
      } else {
        final String proto = exchange.getRequestScheme().equals("https") ? "https" : "http";
        request.getRequestHeaders().put(Headers.X_FORWARDED_PROTO, proto);
        request.putAttachment(ProxiedRequestAttachments.IS_SSL, proto.equals("https"));
      }

      // Set the server name
      if (reuseXForwarded && exchange.getRequestHeaders().contains(Headers.X_FORWARDED_SERVER)) {
        final String hostName = exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_SERVER);
        request.putAttachment(ProxiedRequestAttachments.SERVER_NAME, hostName);
      } else {
        final String hostName = exchange.getHostName();
        request.getRequestHeaders().put(Headers.X_FORWARDED_SERVER, hostName);
        request.putAttachment(ProxiedRequestAttachments.SERVER_NAME, hostName);
      }
      if (!exchange.getRequestHeaders().contains(Headers.X_FORWARDED_HOST)) {
        final String hostName = exchange.getHostName();
        if (hostName != null) {
          request.getRequestHeaders().put(Headers.X_FORWARDED_HOST, hostName);
        }
      }

      // Set the port
      if (reuseXForwarded && exchange.getRequestHeaders().contains(Headers.X_FORWARDED_PORT)) {
        try {
          int port =
              Integer.parseInt(exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_PORT));
          request.putAttachment(ProxiedRequestAttachments.SERVER_PORT, port);
        } catch (NumberFormatException e) {
          int port = exchange.getConnection().getLocalAddress(InetSocketAddress.class).getPort();
          request.getRequestHeaders().put(Headers.X_FORWARDED_PORT, port);
          request.putAttachment(ProxiedRequestAttachments.SERVER_PORT, port);
        }
      } else {
        int port = exchange.getConnection().getLocalAddress(InetSocketAddress.class).getPort();
        request.getRequestHeaders().put(Headers.X_FORWARDED_PORT, port);
        request.putAttachment(ProxiedRequestAttachments.SERVER_PORT, port);
      }

      SSLSessionInfo sslSessionInfo = exchange.getConnection().getSslSessionInfo();
      if (sslSessionInfo != null) {
        X509Certificate[] peerCertificates;
        try {
          peerCertificates = sslSessionInfo.getPeerCertificateChain();
          if (peerCertificates.length > 0) {
            request.putAttachment(
                ProxiedRequestAttachments.SSL_CERT, Certificates.toPem(peerCertificates[0]));
          }
        } catch (SSLPeerUnverifiedException
            | CertificateEncodingException
            | RenegotiationRequiredException e) {
          // ignore
        }
        request.putAttachment(
            ProxiedRequestAttachments.SSL_CYPHER, sslSessionInfo.getCipherSuite());
        request.putAttachment(
            ProxiedRequestAttachments.SSL_SESSION_ID, sslSessionInfo.getSessionId());
      }

      if (rewriteHostHeader) {
        InetSocketAddress targetAddress =
            clientConnection.getConnection().getPeerAddress(InetSocketAddress.class);
        request
            .getRequestHeaders()
            .put(Headers.HOST, targetAddress.getHostString() + ":" + targetAddress.getPort());
        request
            .getRequestHeaders()
            .put(Headers.X_FORWARDED_HOST, exchange.getRequestHeaders().getFirst(Headers.HOST));
      }
      if (log.isDebugEnabled()) {
        log.debugf(
            "Sending request %s to target %s for exchange %s", request, remoteHost, exchange);
      }
      clientConnection
          .getConnection()
          .sendRequest(
              request,
              new ClientCallback<ClientExchange>() {
                @Override
                public void completed(final ClientExchange result) {

                  if (log.isDebugEnabled()) {
                    log.debugf(
                        "Sent request %s to target %s for exchange %s",
                        request, remoteHost, exchange);
                  }
                  result.putAttachment(EXCHANGE, exchange);

                  boolean requiresContinueResponse =
                      HttpContinue.requiresContinueResponse(exchange);
                  if (requiresContinueResponse) {
                    result.setContinueHandler(
                        new ContinueNotification() {
                          @Override
                          public void handleContinue(final ClientExchange clientExchange) {
                            if (log.isDebugEnabled()) {
                              log.debugf(
                                  "Relieved continue response to request %s to target %s for exchange %s",
                                  request, remoteHost, exchange);
                            }
                            HttpContinue.sendContinueResponse(
                                exchange,
                                new IoCallback() {
                                  @Override
                                  public void onComplete(
                                      final HttpServerExchange exchange, final Sender sender) {
                                    // don't care
                                  }

                                  @Override
                                  public void onException(
                                      final HttpServerExchange exchange,
                                      final Sender sender,
                                      final IOException exception) {
                                    IoUtils.safeClose(clientConnection.getConnection());
                                    exchange.endExchange();
                                    UndertowLogger.REQUEST_IO_LOGGER.ioException(exception);
                                  }
                                });
                          }
                        });
                  }

                  // handle server push
                  if (exchange.getConnection().isPushSupported()
                      && result.getConnection().isPushSupported()) {
                    result.setPushHandler(
                        new PushCallback() {
                          @Override
                          public boolean handlePush(
                              ClientExchange originalRequest, final ClientExchange pushedRequest) {

                            if (log.isDebugEnabled()) {
                              log.debugf(
                                  "Sending push request %s received from %s to target %s for exchange %s",
                                  pushedRequest.getRequest(), request, remoteHost, exchange);
                            }
                            final ClientRequest request = pushedRequest.getRequest();
                            exchange
                                .getConnection()
                                .pushResource(
                                    request.getPath(),
                                    request.getMethod(),
                                    request.getRequestHeaders(),
                                    new HttpHandler() {
                                      @Override
                                      public void handleRequest(final HttpServerExchange exchange)
                                          throws Exception {
                                        String path = request.getPath();
                                        int i = path.indexOf("?");
                                        if (i > 0) {
                                          path = path.substring(0, i);
                                        }

                                        exchange.dispatch(
                                            SameThreadExecutor.INSTANCE,
                                            new ProxyAction(
                                                new ProxyConnection(
                                                    pushedRequest.getConnection(), path),
                                                exchange,
                                                requestHeaders,
                                                rewriteHostHeader,
                                                reuseXForwarded));
                                      }
                                    });
                            return true;
                          }
                        });
                  }

                  result.setResponseListener(new ResponseCallback(exchange));
                  final IoExceptionHandler handler =
                      new IoExceptionHandler(exchange, clientConnection.getConnection());
                  if (requiresContinueResponse) {
                    try {
                      if (!result.getRequestChannel().flush()) {
                        result
                            .getRequestChannel()
                            .getWriteSetter()
                            .set(
                                ChannelListeners.flushingChannelListener(
                                    new ChannelListener<StreamSinkChannel>() {
                                      @Override
                                      public void handleEvent(StreamSinkChannel channel) {
                                        Transfer.initiateTransfer(
                                            exchange.getRequestChannel(),
                                            result.getRequestChannel(),
                                            ChannelListeners.closingChannelListener(),
                                            new HTTPTrailerChannelListener(exchange, result),
                                            handler,
                                            handler,
                                            exchange.getConnection().getByteBufferPool());
                                      }
                                    },
                                    handler));
                        result.getRequestChannel().resumeWrites();
                        return;
                      }
                    } catch (IOException e) {
                      handler.handleException(result.getRequestChannel(), e);
                    }
                  }
                  Transfer.initiateTransfer(
                      exchange.getRequestChannel(),
                      result.getRequestChannel(),
                      ChannelListeners.closingChannelListener(),
                      new HTTPTrailerChannelListener(exchange, result),
                      handler,
                      handler,
                      exchange.getConnection().getByteBufferPool());
                }

                @Override
                public void failed(IOException e) {
                  UndertowLogger.PROXY_REQUEST_LOGGER.proxyRequestFailed(
                      exchange.getRequestURI(), e);
                  if (!exchange.isResponseStarted()) {
                    exchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE);
                    exchange.endExchange();
                  } else {
                    IoUtils.safeClose(exchange.getConnection());
                  }
                }
              });
    }
  @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);
      }
    }
  }
  private void initiateRequest(HttpClientExchange httpClientExchange) {
    currentRequest = httpClientExchange;
    pendingResponse = new HttpResponseBuilder();
    ClientRequest request = httpClientExchange.getRequest();

    String connectionString = request.getRequestHeaders().getFirst(CONNECTION);
    if (connectionString != null) {
      HttpString connectionHttpString = new HttpString(connectionString);
      if (connectionHttpString.equals(CLOSE)) {
        state |= CLOSE_REQ;
      } else if (connectionHttpString.equals(UPGRADE)) {
        state |= UPGRADE_REQUESTED;
      }
    } else if (request.getProtocol() != Protocols.HTTP_1_1) {
      state |= CLOSE_REQ;
    }
    if (request.getRequestHeaders().contains(UPGRADE)) {
      state |= UPGRADE_REQUESTED;
    }

    // setup the client request conduits
    final ConduitStreamSourceChannel sourceChannel = connection.getSourceChannel();
    sourceChannel.setReadListener(clientReadListener);
    sourceChannel.resumeReads();

    ConduitStreamSinkChannel sinkChannel = connection.getSinkChannel();
    StreamSinkConduit conduit = originalSinkConduit;
    conduit = new HttpRequestConduit(conduit, bufferPool, request);

    String fixedLengthString = request.getRequestHeaders().getFirst(CONTENT_LENGTH);
    String transferEncodingString = request.getRequestHeaders().getLast(TRANSFER_ENCODING);

    boolean hasContent = true;

    if (fixedLengthString != null) {
      try {
        long length = Long.parseLong(fixedLengthString);
        conduit =
            new ClientFixedLengthStreamSinkConduit(conduit, length, false, false, currentRequest);
        hasContent = length != 0;
      } catch (NumberFormatException e) {
        handleError(new IOException(e));
        return;
      }
    } else if (transferEncodingString != null) {
      if (!transferEncodingString
          .toLowerCase(Locale.ENGLISH)
          .contains(Headers.CHUNKED.toString())) {
        handleError(
            UndertowClientMessages.MESSAGES.unknownTransferEncoding(transferEncodingString));
        return;
      }
      conduit =
          new ChunkedStreamSinkConduit(
              conduit,
              httpClientExchange.getConnection().getBufferPool(),
              false,
              false,
              httpClientExchange.getRequest().getRequestHeaders(),
              requestFinishListener,
              httpClientExchange);
    } else {
      conduit = new ClientFixedLengthStreamSinkConduit(conduit, 0, false, false, currentRequest);
      hasContent = false;
    }
    sinkChannel.setConduit(conduit);

    httpClientExchange.invokeReadReadyCallback(httpClientExchange);
    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.setWriteListener(
              ChannelListeners.flushingChannelListener(
                  null,
                  new ChannelExceptionHandler<ConduitStreamSinkChannel>() {
                    @Override
                    public void handleException(
                        ConduitStreamSinkChannel channel, IOException exception) {
                      handleError(exception);
                    }
                  }));
        }
      } catch (IOException e) {
        handleError(e);
      }
    } else if (!sinkChannel.isWriteResumed()) {
      try {
        // TODO: this needs some more thought
        if (!sinkChannel.flush()) {
          sinkChannel.setWriteListener(
              new ChannelListener<ConduitStreamSinkChannel>() {
                @Override
                public void handleEvent(ConduitStreamSinkChannel channel) {
                  try {
                    if (channel.flush()) {
                      channel.suspendWrites();
                    }
                  } catch (IOException e) {
                    handleError(e);
                  }
                }
              });
          sinkChannel.resumeWrites();
        }
      } catch (IOException e) {
        handleError(e);
      }
    }
  }