@Override
  public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
    if (httpChildChannel != null) {
      HttpChildChannel httpChildChannel = this.httpChildChannel;
      this.httpChildChannel = null;

      if (httpChildChannel.setReadClosed() || httpChildChannel.setWriteClosed()) {
        fireExceptionCaught(httpChildChannel, e.getCause());
        fireChannelClosed(httpChildChannel);
      }
    }

    Channel channel = ctx.getChannel();
    channel.close();
  }
  @Override
  public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
    HttpChildChannel httpChildChannel = this.httpChildChannel;
    if (httpChildChannel != null) {

      this.httpChildChannel = null;

      switch (httpChildChannel.readState()) {
        case UPGRADED:
          if (httpChildChannel.setReadClosed()) {
            fireChannelDisconnected(httpChildChannel);
            fireChannelUnbound(httpChildChannel);
            fireChannelClosed(httpChildChannel);
          }
          break;
        case CONTENT_COMPLETE:
          break;
        default:
          ChannelException exception = new ChannelException("Channel closed unexpectedly");
          exception.fillInStackTrace();
          fireExceptionCaught(httpChildChannel, exception);
          break;
      }

      switch (httpChildChannel.writeState()) {
        case UPGRADED:
        case CONTENT_CLOSE:
          if (httpChildChannel.setWriteClosed()) {
            fireChannelDisconnected(httpChildChannel);
            fireChannelUnbound(httpChildChannel);
            fireChannelClosed(httpChildChannel);
          }
          break;
        case CONTENT_COMPLETE:
          break;
        default:
          ChannelException exception = new ChannelException("Channel closed unexpectedly");
          exception.fillInStackTrace();
          fireExceptionCaught(httpChildChannel, exception);
          break;
      }
    }
  }
  @Override
  protected void httpMessageReceived(
      ChannelHandlerContext ctx, MessageEvent e, HttpChunk httpMessage) throws Exception {
    ChannelBuffer content = httpMessage.getContent();
    if (content.readable()) {
      fireMessageReceived(httpChildChannel, content);
    }

    boolean last = httpMessage.isLast();
    if (last) {
      HttpChildChannel httpChildChannel = this.httpChildChannel;
      httpChildChannel.readState(HttpReadState.CONTENT_COMPLETE);
      this.httpChildChannel = null;
      fireInputShutdown(httpChildChannel);
      if (httpChildChannel.setReadClosed()) {
        fireChannelDisconnected(httpChildChannel);
        fireChannelUnbound(httpChildChannel);
        fireChannelClosed(httpChildChannel);
      }
    }
  }
  @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;
    }
  }