@Override
  public void populateCamelHeaders(
      HttpResponse response,
      Map<String, Object> headers,
      Exchange exchange,
      NettyHttpConfiguration configuration)
      throws Exception {
    LOG.trace("populateCamelHeaders: {}", response);

    headers.put(Exchange.HTTP_RESPONSE_CODE, response.getStatus().getCode());
    headers.put(Exchange.HTTP_RESPONSE_TEXT, response.getStatus().getReasonPhrase());

    for (String name : response.headers().names()) {
      // mapping the content-type
      if (name.toLowerCase().equals("content-type")) {
        name = Exchange.CONTENT_TYPE;
      }
      // add the headers one by one, and use the header filter strategy
      List<String> values = response.headers().getAll(name);
      Iterator<?> it = ObjectHelper.createIterator(values);
      while (it.hasNext()) {
        Object extracted = it.next();
        LOG.trace("HTTP-header: {}", extracted);
        if (headerFilterStrategy != null
            && !headerFilterStrategy.applyFilterToExternalHeaders(name, extracted, exchange)) {
          NettyHttpHelper.appendHeader(headers, name, extracted);
        }
      }
    }
  }
  @Override
  protected Object getRequestBody(Exchange exchange) throws Exception {
    // creating the url to use takes 2-steps
    String uri = NettyHttpHelper.createURL(exchange, getEndpoint());
    URI u = NettyHttpHelper.createURI(exchange, uri, getEndpoint());

    HttpRequest request =
        getEndpoint()
            .getNettyHttpBinding()
            .toNettyRequest(exchange.getIn(), u.toString(), getConfiguration());
    String actualUri = request.getUri();
    exchange.getIn().setHeader(Exchange.HTTP_URL, actualUri);
    // Need to check if we need to close the connection or not
    if (!HttpHeaders.isKeepAlive(request)) {
      // just want to make sure we close the channel if the keepAlive is not true
      exchange.setProperty(NettyConstants.NETTY_CLOSE_CHANNEL_WHEN_COMPLETE, true);
    }
    if (getConfiguration().isBridgeEndpoint()) {
      // Need to remove the Host key as it should be not used when bridging/proxying
      exchange.getIn().removeHeader("host");
    }

    return request;
  }
    @Override
    public void done(boolean doneSync) {
      try {
        NettyHttpMessage nettyMessage =
            exchange.hasOut()
                ? exchange.getOut(NettyHttpMessage.class)
                : exchange.getIn(NettyHttpMessage.class);
        if (nettyMessage != null) {
          HttpResponse response = nettyMessage.getHttpResponse();
          if (response != null) {
            // the actual url is stored on the IN message in the getRequestBody method as its
            // accessed on-demand
            String actualUrl = exchange.getIn().getHeader(Exchange.HTTP_URL, String.class);
            int code = response.getStatus() != null ? response.getStatus().getCode() : -1;
            log.debug("Http responseCode: {}", code);

            // if there was a http error code (300 or higher) then check if we should throw an
            // exception
            if (code >= 300 && getConfiguration().isThrowExceptionOnFailure()) {
              // operation failed so populate exception to throw
              Exception cause =
                  NettyHttpHelper.populateNettyHttpOperationFailedException(
                      exchange,
                      actualUrl,
                      response,
                      code,
                      getConfiguration().isTransferException());
              exchange.setException(cause);
            }
          }
        }
      } finally {
        // ensure we call the delegated callback
        callback.done(doneSync);
      }
    }
  @Override
  public void populateCamelHeaders(
      HttpRequest request,
      Map<String, Object> headers,
      Exchange exchange,
      NettyHttpConfiguration configuration)
      throws Exception {
    LOG.trace("populateCamelHeaders: {}", request);

    // NOTE: these headers is applied using the same logic as camel-http/camel-jetty to be
    // consistent

    headers.put(Exchange.HTTP_METHOD, request.getMethod().getName());
    // strip query parameters from the uri
    String s = request.getUri();
    if (s.contains("?")) {
      s = ObjectHelper.before(s, "?");
    }

    // we want the full path for the url, as the client may provide the url in the HTTP headers as
    // absolute or relative, eg
    //   /foo
    //   http://servername/foo
    String http = configuration.isSsl() ? "https://" : "http://";
    if (!s.startsWith(http)) {
      if (configuration.getPort() != 80) {
        s = http + configuration.getHost() + ":" + configuration.getPort() + s;
      } else {
        s = http + configuration.getHost() + s;
      }
    }

    headers.put(Exchange.HTTP_URL, s);
    // uri is without the host and port
    URI uri = new URI(request.getUri());
    // uri is path and query parameters
    headers.put(Exchange.HTTP_URI, uri.getPath());
    headers.put(Exchange.HTTP_QUERY, uri.getQuery());
    headers.put(Exchange.HTTP_RAW_QUERY, uri.getRawQuery());

    // strip the starting endpoint path so the path is relative to the endpoint uri
    String path = uri.getPath();
    if (configuration.getPath() != null && path.startsWith(configuration.getPath())) {
      path = path.substring(configuration.getPath().length());
    }
    headers.put(Exchange.HTTP_PATH, path);

    if (LOG.isTraceEnabled()) {
      LOG.trace("HTTP-Method {}", request.getMethod().getName());
      LOG.trace("HTTP-Uri {}", request.getUri());
    }

    for (String name : request.headers().names()) {
      // mapping the content-type
      if (name.toLowerCase(Locale.US).equals("content-type")) {
        name = Exchange.CONTENT_TYPE;
      }

      if (name.toLowerCase(Locale.US).equals("authorization")) {
        String value = request.headers().get(name);
        // store a special header that this request was authenticated using HTTP Basic
        if (value != null && value.trim().startsWith("Basic")) {
          if (headerFilterStrategy != null
              && !headerFilterStrategy.applyFilterToExternalHeaders(
                  NettyHttpConstants.HTTP_AUTHENTICATION, "Basic", exchange)) {
            NettyHttpHelper.appendHeader(headers, NettyHttpConstants.HTTP_AUTHENTICATION, "Basic");
          }
        }
      }

      // add the headers one by one, and use the header filter strategy
      List<String> values = request.headers().getAll(name);
      Iterator<?> it = ObjectHelper.createIterator(values);
      while (it.hasNext()) {
        Object extracted = it.next();
        Object decoded = shouldUrlDecodeHeader(configuration, name, extracted, "UTF-8");
        LOG.trace("HTTP-header: {}", extracted);
        if (headerFilterStrategy != null
            && !headerFilterStrategy.applyFilterToExternalHeaders(name, decoded, exchange)) {
          NettyHttpHelper.appendHeader(headers, name, decoded);
        }
      }
    }

    // add uri parameters as headers to the Camel message
    if (request.getUri().contains("?")) {
      String query = ObjectHelper.after(request.getUri(), "?");
      Map<String, Object> uriParameters = URISupport.parseQuery(query, false, true);

      for (Map.Entry<String, Object> entry : uriParameters.entrySet()) {
        String name = entry.getKey();
        Object values = entry.getValue();
        Iterator<?> it = ObjectHelper.createIterator(values);
        while (it.hasNext()) {
          Object extracted = it.next();
          Object decoded = shouldUrlDecodeHeader(configuration, name, extracted, "UTF-8");
          LOG.trace("URI-Parameter: {}", extracted);
          if (headerFilterStrategy != null
              && !headerFilterStrategy.applyFilterToExternalHeaders(name, decoded, exchange)) {
            NettyHttpHelper.appendHeader(headers, name, decoded);
          }
        }
      }
    }

    // if body is application/x-www-form-urlencoded then extract the body as query string and append
    // as headers
    // if it is a bridgeEndpoint we need to skip this part of work
    if (request.getMethod().getName().equals("POST")
        && request.headers().get(Exchange.CONTENT_TYPE) != null
        && request
            .headers()
            .get(Exchange.CONTENT_TYPE)
            .startsWith(NettyHttpConstants.CONTENT_TYPE_WWW_FORM_URLENCODED)
        && !configuration.isBridgeEndpoint()) {

      String charset = "UTF-8";

      // Push POST form params into the headers to retain compatibility with DefaultHttpBinding
      String body = request.getContent().toString(Charset.forName(charset));
      if (ObjectHelper.isNotEmpty(body)) {
        for (String param : body.split("&")) {
          String[] pair = param.split("=", 2);
          if (pair.length == 2) {
            String name = shouldUrlDecodeHeader(configuration, "", pair[0], charset);
            String value = shouldUrlDecodeHeader(configuration, name, pair[1], charset);
            if (headerFilterStrategy != null
                && !headerFilterStrategy.applyFilterToExternalHeaders(name, value, exchange)) {
              NettyHttpHelper.appendHeader(headers, name, value);
            }
          } else {
            throw new IllegalArgumentException(
                "Invalid parameter, expected to be a pair but was " + param);
          }
        }
      }
    }
  }
  @Override
  public HttpRequest toNettyRequest(
      Message message, String uri, NettyHttpConfiguration configuration) throws Exception {
    LOG.trace("toNettyRequest: {}", message);

    // the message body may already be a Netty HTTP response
    if (message.getBody() instanceof HttpRequest) {
      return (HttpRequest) message.getBody();
    }

    // just assume GET for now, we will later change that to the actual method to use
    HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri);

    TypeConverter tc = message.getExchange().getContext().getTypeConverter();

    // if we bridge endpoint then we need to skip matching headers with the HTTP_QUERY to avoid
    // sending
    // duplicated headers to the receiver, so use this skipRequestHeaders as the list of headers to
    // skip
    Map<String, Object> skipRequestHeaders = null;
    if (configuration.isBridgeEndpoint()) {
      String queryString = message.getHeader(Exchange.HTTP_QUERY, String.class);
      if (queryString != null) {
        skipRequestHeaders = URISupport.parseQuery(queryString, false, true);
      }
      // Need to remove the Host key as it should be not used
      message.getHeaders().remove("host");
    }

    // append headers
    // must use entrySet to ensure case of keys is preserved
    for (Map.Entry<String, Object> entry : message.getHeaders().entrySet()) {
      String key = entry.getKey();
      Object value = entry.getValue();

      // we should not add headers for the parameters in the uri if we bridge the endpoint
      // as then we would duplicate headers on both the endpoint uri, and in HTTP headers as well
      if (skipRequestHeaders != null && skipRequestHeaders.containsKey(key)) {
        continue;
      }

      // use an iterator as there can be multiple values. (must not use a delimiter)
      final Iterator<?> it = ObjectHelper.createIterator(value, null, true);
      while (it.hasNext()) {
        String headerValue = tc.convertTo(String.class, it.next());

        if (headerValue != null
            && headerFilterStrategy != null
            && !headerFilterStrategy.applyFilterToCamelHeaders(
                key, headerValue, message.getExchange())) {
          LOG.trace("HTTP-Header: {}={}", key, headerValue);
          request.headers().add(key, headerValue);
        }
      }
    }

    Object body = message.getBody();
    if (body != null) {
      // support bodies as native Netty
      ChannelBuffer buffer;
      if (body instanceof ChannelBuffer) {
        buffer = (ChannelBuffer) body;
      } else {
        // try to convert to buffer first
        buffer = message.getBody(ChannelBuffer.class);
        if (buffer == null) {
          // fallback to byte array as last resort
          byte[] data = message.getMandatoryBody(byte[].class);
          buffer = ChannelBuffers.copiedBuffer(data);
        }
      }
      if (buffer != null) {
        request.setContent(buffer);
        int len = buffer.readableBytes();
        // set content-length
        request.headers().set(HttpHeaders.Names.CONTENT_LENGTH, len);
        LOG.trace("Content-Length: {}", len);
      } else {
        // we do not support this kind of body
        throw new NoTypeConversionAvailableException(body, ChannelBuffer.class);
      }
    }

    // update HTTP method accordingly as we know if we have a body or not
    HttpMethod method = NettyHttpHelper.createMethod(message, body != null);
    request.setMethod(method);

    // set the content type in the response.
    String contentType = MessageHelper.getContentType(message);
    if (contentType != null) {
      // set content-type
      request.headers().set(HttpHeaders.Names.CONTENT_TYPE, contentType);
      LOG.trace("Content-Type: {}", contentType);
    }

    // must include HOST header as required by HTTP 1.1
    // use URI as its faster than URL (no DNS lookup)
    URI u = new URI(uri);
    String host = u.getHost();
    request.headers().set(HttpHeaders.Names.HOST, host);
    LOG.trace("Host: {}", host);

    // configure connection to accordingly to keep alive configuration
    // favor using the header from the message
    String connection = message.getHeader(HttpHeaders.Names.CONNECTION, String.class);
    if (connection == null) {
      // fallback and use the keep alive from the configuration
      if (configuration.isKeepAlive()) {
        connection = HttpHeaders.Values.KEEP_ALIVE;
      } else {
        connection = HttpHeaders.Values.CLOSE;
      }
    }
    request.headers().set(HttpHeaders.Names.CONNECTION, connection);
    LOG.trace("Connection: {}", connection);
    return request;
  }