/** Free any request/response handlers related to the current channel handler context. */
  public void freeHandlers(final ChannelHandlerContext ctx) {

    final PooledServerResponse response = ctx.attr(ATTR_RESPONSE).getAndRemove();

    if (response != null) {

      try {

        final RequestHandler handler = response.handler();

        if (handler != null) {
          handler.onComplete(response.request(), response);
        }

      } finally {

        response.request().release();
        messagePool.makeAvailable(response.request());

        response.close();

        messagePool.makeAvailable(response);
      }
    }
  }
  @Override
  public void channelInactive(final ChannelHandlerContext ctx) {

    final PooledServerResponse response = ctx.attr(ATTR_RESPONSE).get();

    if (response != null) {

      try {

        if (!response.isFinished()) {

          response.close();

          final RequestHandler handler = response.handler();

          if (handler != null) {
            handler.onAbort(response.request(), response);
          }
        }

      } finally {

        freeHandlers(ctx);
      }
    }
  }
  @Override
  public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable exception)
      throws Exception {

    final PooledServerResponse response = ctx.attr(ATTR_RESPONSE).get();

    if (response != null) {

      try {

        try {

          if (!response.isFinished()) {

            response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);

            config.errorHandler().onError(response.request(), response, exception);

            response.close();

            final RequestHandler handler = response.handler();

            if (handler != null) {
              handler.onException(response.request(), response, exception);
            }
          }

        } finally {

          config.logger().error(response.request(), response, exception);
        }

      } finally {

        freeHandlers(ctx);
      }
    }
  }
  @Override
  public void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest msg)
      throws Exception {

    final RequestHandlerMapping mapping = config.getRequestMapping(msg.getUri());

    String relativePath = msg.getUri();

    if (mapping != null) {
      relativePath = relativePath.substring(mapping.path().length());
    }

    // Create request/response
    final PooledServerRequest request = messagePool.getRequest();

    // Handle 503 - sanity check, should be caught in acceptor
    if (request == null) {
      sendServerError(ctx, new ServerTooBusyException("Maximum concurrent connections reached"));
      return;
    }

    request.init(ctx.channel(), msg, relativePath);

    final RequestHandler handler = mapping == null ? null : mapping.handler(request);

    final PooledServerResponse response = messagePool.getResponse();
    response.init(ctx, this, handler, request, config.logger());

    if (mapping == null) {
      // No handler found, 404
      response.setStatus(HttpResponseStatus.NOT_FOUND);
    }

    // Store in ChannelHandlerContext for future reference
    ctx.attr(ATTR_RESPONSE).set(response);

    try {

      // MJS: Dispatch an error if not found or authorized
      if (response.getStatus() == HttpResponseStatus.UNAUTHORIZED
          || response.getStatus() == HttpResponseStatus.NOT_FOUND) {
        config.errorHandler().onError(request, response, null);
      } else {
        handler.onRequest(request, response);
      }

    } catch (final Throwable t) {

      // Catch server errors
      response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);

      try {
        config.errorHandler().onError(request, response, t);
      } catch (final Throwable t2) {
        response.write(
            t.getClass()
                + " was thrown while processing this request.  Additionally, "
                + t2.getClass()
                + " was thrown while handling this exception.");
      }

      config.logger().error(request, response, t);

      // Force request to end on exception, async handlers cannot allow
      // unchecked exceptions and still expect to return data
      if (!response.isFinished()) {
        response.finish();
      }

    } finally {

      // If handler did not request async response, finish request
      if (!response.isFinished() && !response.isSuspended()) {
        response.finish();
      }
    }
  }