private void writeResponse(
      ChannelHandlerContext ctx, MessageEvent e, RequestV2 request, InetAddress ia) {
    // Decide whether to close the connection or not.
    boolean close =
        HttpHeaders.Values.CLOSE.equalsIgnoreCase(
                nettyRequest.headers().get(HttpHeaders.Names.CONNECTION))
            || nettyRequest.getProtocolVersion().equals(HttpVersion.HTTP_1_0)
                && !HttpHeaders.Values.KEEP_ALIVE.equalsIgnoreCase(
                    nettyRequest.headers().get(HttpHeaders.Names.CONNECTION));

    // Build the response object.
    HttpResponse response;
    if (request.getLowRange() != 0 || request.getHighRange() != 0) {
      response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.PARTIAL_CONTENT);
    } else {
      String soapAction = nettyRequest.headers().get("SOAPACTION");

      if (soapAction != null && soapAction.contains("X_GetFeatureList")) {
        LOGGER.debug("Invalid action in SOAPACTION: " + soapAction);
        response =
            new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR);
      } else {
        response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
      }
    }

    StartStopListenerDelegate startStopListenerDelegate =
        new StartStopListenerDelegate(ia.getHostAddress());
    // Attach it to the context so it can be invoked if connection is reset unexpectedly
    ctx.setAttachment(startStopListenerDelegate);

    try {
      request.answer(response, e, close, startStopListenerDelegate);
    } catch (IOException e1) {
      LOGGER.trace("HTTP request V2 IO error: " + e1.getMessage());
      // note: we don't call stop() here in a finally block as
      // answer() is non-blocking. we only (may) need to call it
      // here in the case of an exception. it's a no-op if it's
      // already been called
      startStopListenerDelegate.stop();
    }
  }
  @Override
  public void handle(HttpRequest request, Channel channel) throws Exception {
    // 设置调用方ip,X-Forwarded-For 是使用了代理(如nginx)会附加在HTTP头域上的
    if (request.headers().contains("X-Forwarded-For")) {
      request.headers().set(Names.HOST, request.headers().get("X-Forwarded-For"));
    } else {
      InetSocketAddress insocket = (InetSocketAddress) channel.getRemoteAddress();
      request.headers().set(Names.HOST, insocket.getAddress().getHostAddress());
    }

    HttpResponse response =
        new NaviHttpResponse(request.getProtocolVersion(), HttpResponseStatus.OK);

    for (INaviHttpRequestListener listener : listeners) {
      boolean res = listener.process(request, response);
      log.debug(listener.getClass().getName() + " is completed!");
      if (!res) {
        break;
      }
    }

    sendHttpResponse(channel, request, response);
  }
  @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;
  }
  @Override
  public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
    RequestV2 request = null;
    RendererConfiguration renderer = null;
    String userAgentString = null;
    StringBuilder unknownHeaders = new StringBuilder();
    String separator = "";

    HttpRequest nettyRequest = this.nettyRequest = (HttpRequest) e.getMessage();

    InetSocketAddress remoteAddress = (InetSocketAddress) e.getChannel().getRemoteAddress();
    InetAddress ia = remoteAddress.getAddress();

    // Apply the IP filter
    if (filterIp(ia)) {
      e.getChannel().close();
      LOGGER.trace("Access denied for address " + ia + " based on IP filter");
      return;
    }

    LOGGER.trace("Opened request handler on socket " + remoteAddress);
    PMS.get().getRegistry().disableGoToSleep();
    request = new RequestV2(nettyRequest.getMethod().getName(), nettyRequest.getUri().substring(1));
    LOGGER.trace(
        "Request: "
            + nettyRequest.getProtocolVersion().getText()
            + " : "
            + request.getMethod()
            + " : "
            + request.getArgument());

    if (nettyRequest.getProtocolVersion().getMinorVersion() == 0) {
      request.setHttp10(true);
    }

    // The handler makes a couple of attempts to recognize a renderer from its requests.
    // IP address matches from previous requests are preferred, when that fails request
    // header matches are attempted and if those fail as well we're stuck with the
    // default renderer.

    // Attempt 1: try to recognize the renderer by its socket address from previous requests
    renderer = RendererConfiguration.getRendererConfigurationBySocketAddress(ia);

    if (renderer != null) {
      PMS.get().setRendererFound(renderer);
      request.setMediaRenderer(renderer);
      LOGGER.trace(
          "Matched media renderer \"" + renderer.getRendererName() + "\" based on address " + ia);
    }

    Set<String> headerNames = nettyRequest.headers().names();
    Iterator<String> iterator = headerNames.iterator();
    while (iterator.hasNext()) {
      String name = iterator.next();
      String headerLine = name + ": " + nettyRequest.headers().get(name);
      LOGGER.trace("Received on socket: " + headerLine);

      if (renderer == null && headerLine.toUpperCase().startsWith("USER-AGENT")) {
        userAgentString = headerLine.substring(headerLine.indexOf(':') + 1).trim();

        // Attempt 2: try to recognize the renderer by matching the "User-Agent" header
        renderer = RendererConfiguration.getRendererConfigurationByUA(userAgentString);

        if (renderer != null) {
          request.setMediaRenderer(renderer);
          renderer.associateIP(ia); // Associate IP address for later requests
          PMS.get().setRendererFound(renderer);
          LOGGER.trace(
              "Matched media renderer \""
                  + renderer.getRendererName()
                  + "\" based on header \""
                  + headerLine
                  + "\"");
        }
      }

      if (renderer == null) {
        // Attempt 3: try to recognize the renderer by matching an additional header
        renderer = RendererConfiguration.getRendererConfigurationByUAAHH(headerLine);

        if (renderer != null) {
          request.setMediaRenderer(renderer);
          renderer.associateIP(ia); // Associate IP address for later requests
          PMS.get().setRendererFound(renderer);
          LOGGER.trace(
              "Matched media renderer \""
                  + renderer.getRendererName()
                  + "\" based on header \""
                  + headerLine
                  + "\"");
        }
      }

      try {
        StringTokenizer s = new StringTokenizer(headerLine);
        String temp = s.nextToken();
        if (temp.toUpperCase().equals("SOAPACTION:")) {
          request.setSoapaction(s.nextToken());
        } else if (temp.toUpperCase().equals("CALLBACK:")) {
          request.setSoapaction(s.nextToken());
        } else if (headerLine.toUpperCase().contains("RANGE: BYTES=")) {
          String nums =
              headerLine.substring(headerLine.toUpperCase().indexOf("RANGE: BYTES=") + 13).trim();
          StringTokenizer st = new StringTokenizer(nums, "-");
          if (!nums.startsWith("-")) {
            request.setLowRange(Long.parseLong(st.nextToken()));
          }
          if (!nums.startsWith("-") && !nums.endsWith("-")) {
            request.setHighRange(Long.parseLong(st.nextToken()));
          } else {
            request.setHighRange(-1);
          }
        } else if (headerLine.toLowerCase().contains("transfermode.dlna.org:")) {
          request.setTransferMode(
              headerLine
                  .substring(headerLine.toLowerCase().indexOf("transfermode.dlna.org:") + 22)
                  .trim());
        } else if (headerLine.toLowerCase().contains("getcontentfeatures.dlna.org:")) {
          request.setContentFeatures(
              headerLine
                  .substring(headerLine.toLowerCase().indexOf("getcontentfeatures.dlna.org:") + 28)
                  .trim());
        } else {
          Matcher matcher = TIMERANGE_PATTERN.matcher(headerLine);
          if (matcher.find()) {
            String first = matcher.group(1);
            if (first != null) {
              request.setTimeRangeStartString(first);
            }
            String end = matcher.group(2);
            if (end != null) {
              request.setTimeRangeEndString(end);
            }
          } else {
            // If we made it to here, none of the previous header checks matched.
            // Unknown headers make interesting logging info when we cannot recognize
            // the media renderer, so keep track of the truly unknown ones.
            boolean isKnown = false;

            // Try to match known headers.
            String lowerCaseHeaderLine = headerLine.toLowerCase();
            for (String knownHeaderString : KNOWN_HEADERS) {
              if (lowerCaseHeaderLine.startsWith(knownHeaderString)) {
                isKnown = true;
                break;
              }
            }

            if (!isKnown) {
              // Truly unknown header, therefore interesting. Save for later use.
              unknownHeaders.append(separator).append(headerLine);
              separator = ", ";
            }
          }
        }
      } catch (Exception ee) {
        LOGGER.error("Error parsing HTTP headers", ee);
      }
    }

    // Still no media renderer recognized?
    if (request.getMediaRenderer() == null) {

      // Attempt 4: Not really an attempt; all other attempts to recognize
      // the renderer have failed. The only option left is to assume the
      // default renderer.
      request.setMediaRenderer(RendererConfiguration.getDefaultConf());
      LOGGER.trace("Using default media renderer: " + request.getMediaRenderer().getRendererName());

      if (userAgentString != null && !userAgentString.equals("FDSSDP")) {
        // We have found an unknown renderer
        LOGGER.info(
            "Media renderer was not recognized. Possible identifying HTTP headers: User-Agent: "
                + userAgentString
                + ("".equals(unknownHeaders.toString()) ? "" : ", " + unknownHeaders.toString()));
        PMS.get().setRendererFound(request.getMediaRenderer());
      }
    } else {
      if (userAgentString != null) {
        LOGGER.debug("HTTP User-Agent: " + userAgentString);
      }

      LOGGER.trace("Recognized media renderer: " + request.getMediaRenderer().getRendererName());
    }

    if (nettyRequest.headers().contains(HttpHeaders.Names.CONTENT_LENGTH)) {
      byte data[] = new byte[(int) HttpHeaders.getContentLength(nettyRequest)];
      ChannelBuffer content = nettyRequest.getContent();
      content.readBytes(data);
      request.setTextContent(new String(data, "UTF-8"));
    }

    LOGGER.trace(
        "HTTP: "
            + request.getArgument()
            + " / "
            + request.getLowRange()
            + "-"
            + request.getHighRange());

    writeResponse(e, request, ia);
  }
  @Override
  protected void httpMessageReceived(
      ChannelHandlerContext ctx, MessageEvent e, HttpRequest httpRequest) throws Exception {

    HttpVersion version = httpRequest.getProtocolVersion();
    URI httpLocation = getEffectiveURI(httpRequest);
    if (httpLocation == null) {
      // see RFC-7230 section 5.4 Host
      HttpResponse httpResponse = new DefaultHttpResponse(version, BAD_REQUEST);
      ChannelFuture future = future(ctx.getChannel());
      write(ctx, future, httpResponse);
      return;
    }

    // channel's local address is resolved address so get the bind address from
    // server channel's attachment
    ChannelAddress transportCandidate =
        (ChannelAddress) ctx.getChannel().getParent().getAttachment();
    ChannelAddress candidate = new ChannelAddress(httpLocation, transportCandidate);

    Entry<ChannelAddress, HttpServerChannel> httpBinding = httpBindings.floorEntry(candidate);

    if (httpBinding == null) {
      HttpResponse httpResponse = new DefaultHttpResponse(version, NOT_FOUND);
      ChannelFuture future = future(ctx.getChannel());
      write(ctx, future, httpResponse);
      return;
    }

    HttpServerChannel parent = httpBinding.getValue();
    ChannelFactory factory = parent.getFactory();
    ChannelConfig config = parent.getConfig();
    ChannelPipelineFactory pipelineFactory = config.getPipelineFactory();
    ChannelPipeline pipeline = pipelineFactory.getPipeline();
    ChannelAddress httpLocalAddress = parent.getLocalAddress();

    Channel transport = ctx.getChannel();
    ChannelAddress remoteAddress = remoteAddress(transport);
    ChannelAddress httpRemoteAddress = new ChannelAddress(httpLocation, remoteAddress, true);

    HttpChildChannelSink sink = new HttpChildChannelSink(transport);
    HttpChildChannel httpChildChannel = new HttpChildChannel(parent, factory, pipeline, sink);
    HttpChannelConfig httpChildConfig = httpChildChannel.getConfig();
    httpChildConfig.setMethod(httpRequest.getMethod());
    httpChildConfig.setVersion(version);
    httpChildConfig.getReadHeaders().set(httpRequest.headers());
    httpChildConfig.setReadQuery(new QueryStringDecoder(httpRequest.getUri()));
    httpChildConfig.setWriteQuery(new QueryStringEncoder(httpRequest.getUri()));
    httpChildConfig.setStatus(HttpResponseStatus.OK);

    this.httpChildChannel = httpChildChannel;

    ChannelBuffer content = httpRequest.getContent();

    // update read state before firing channel events
    if (isTransferEncodingChunked(httpRequest)) {
      httpChildChannel.readState(HttpReadState.CONTENT_CHUNKED);
    } else if (isContentLengthSet(httpRequest)) {
      long contentLength = getContentLength(httpRequest);
      contentLength -= content.readableBytes();
      if (contentLength > 0) {
        httpChildChannel.readState(HttpReadState.CONTENT_CHUNKED);
      } else {
        httpChildChannel.readState(HttpReadState.CONTENT_COMPLETE);
      }
    } else {
      // see RFC-7230 section 3.3
      // content indicated by presence of Content-Length or Transfer-Encoding
      httpChildChannel.readState(HttpReadState.CONTENT_COMPLETE);
    }

    fireChannelOpen(httpChildChannel);

    httpChildChannel.setLocalAddress(httpLocalAddress);
    httpChildChannel.setBound();
    fireChannelBound(httpChildChannel, httpLocalAddress);

    httpChildChannel.setRemoteAddress(httpRemoteAddress);
    httpChildChannel.setConnected();
    fireChannelConnected(httpChildChannel, httpRemoteAddress);

    if (content.readable()) {
      fireMessageReceived(httpChildChannel, content);
    }

    // note: status may be set in reaction to one of the above events, such as CONNECTED
    //       so defer status code check until this point
    if (httpChildConfig.getStatus().getCode() == SWITCHING_PROTOCOLS.getCode()) {
      httpChildChannel.readState(HttpReadState.UPGRADED);
    }

    switch (httpChildChannel.readState()) {
      case CONTENT_COMPLETE:
        fireInputShutdown(httpChildChannel);
        this.httpChildChannel = null;
        if (httpChildChannel.setReadClosed()) {
          fireChannelDisconnected(httpChildChannel);
          fireChannelUnbound(httpChildChannel);
          fireChannelClosed(httpChildChannel);
        }
        break;
      default:
        break;
    }
  }
  public NettyRequest newNettyRequest(
      Request request, boolean forceConnect, ProxyServer proxyServer) throws IOException {

    Uri uri = request.getUri();
    HttpMethod method = forceConnect ? HttpMethod.CONNECT : HttpMethod.valueOf(request.getMethod());
    boolean connect = method == HttpMethod.CONNECT;

    boolean allowConnectionPooling =
        config.isAllowPoolingConnections()
            && (!HttpUtils.isSecure(uri) || config.isAllowPoolingSslConnections());

    HttpVersion httpVersion =
        !allowConnectionPooling || (connect && proxyServer.isForceHttp10())
            ? HttpVersion.HTTP_1_0
            : HttpVersion.HTTP_1_1;
    String requestUri = requestUri(uri, proxyServer, connect);

    NettyBody body = body(request, connect);

    HttpRequest httpRequest;
    NettyRequest nettyRequest;
    if (body instanceof NettyDirectBody) {
      ChannelBuffer buffer = NettyDirectBody.class.cast(body).channelBuffer();
      httpRequest = new DefaultHttpRequest(httpVersion, method, requestUri);
      // body is passed as null as it's written directly with the request
      httpRequest.setContent(buffer);
      nettyRequest = new NettyRequest(httpRequest, null);

    } else {
      httpRequest = new DefaultHttpRequest(httpVersion, method, requestUri);
      nettyRequest = new NettyRequest(httpRequest, body);
    }

    HttpHeaders headers = httpRequest.headers();

    if (!connect) {
      // assign headers as configured on request
      for (Entry<String, List<String>> header : request.getHeaders()) {
        headers.set(header.getKey(), header.getValue());
      }

      if (isNonEmpty(request.getCookies()))
        headers.set(COOKIE, CookieEncoder.encode(request.getCookies()));

      if (config.isCompressionEnforced() && !headers.contains(ACCEPT_ENCODING))
        headers.set(ACCEPT_ENCODING, GZIP_DEFLATE);
    }

    if (body != null) {
      if (body.getContentLength() < 0) headers.set(TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
      else headers.set(CONTENT_LENGTH, body.getContentLength());

      if (body.getContentType() != null) headers.set(CONTENT_TYPE, body.getContentType());
    }

    // connection header and friends
    boolean webSocket = isWebSocket(uri.getScheme());
    if (!connect && webSocket) {
      String origin =
          "http://"
              + uri.getHost()
              + ":"
              + (uri.getPort() == -1 ? isSecure(uri.getScheme()) ? 443 : 80 : uri.getPort());
      headers
          .set(UPGRADE, HttpHeaders.Values.WEBSOCKET) //
          .set(CONNECTION, HttpHeaders.Values.UPGRADE) //
          .set(ORIGIN, origin) //
          .set(SEC_WEBSOCKET_KEY, getKey()) //
          .set(SEC_WEBSOCKET_VERSION, "13");

    } else if (!headers.contains(CONNECTION)) {
      String connectionHeaderValue =
          connectionHeader(allowConnectionPooling, httpVersion == HttpVersion.HTTP_1_1);
      if (connectionHeaderValue != null) headers.set(CONNECTION, connectionHeaderValue);
    }

    if (!headers.contains(HOST)) headers.set(HOST, hostHeader(request, uri));

    Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm();

    // don't override authorization but append
    addAuthorizationHeader(headers, systematicAuthorizationHeader(request, realm));

    setProxyAuthorizationHeader(
        headers, systematicProxyAuthorizationHeader(request, proxyServer, realm, connect));

    // Add default accept headers
    if (!headers.contains(ACCEPT)) headers.set(ACCEPT, "*/*");

    // Add default user agent
    if (!headers.contains(USER_AGENT) && config.getUserAgent() != null)
      headers.set(USER_AGENT, config.getUserAgent());

    return nettyRequest;
  }