@Override
 public void onResponseComplete(Exception exception) {
   long responseCompleteStartTime = System.currentTimeMillis();
   try {
     if (responseCompleteCalled.compareAndSet(false, true)) {
       logger.trace("Finished responding to current request on channel {}", ctx.channel());
       nettyMetrics.requestCompletionRate.mark();
       if (exception == null) {
         if (!maybeWriteResponseMetadata(responseMetadata, responseMetadataWriteListener)) {
           // There were other writes. Let ChunkedWriteHandler finish if it has been kicked off.
           chunkedWriteHandler.resumeTransfer();
         }
       } else {
         log(exception);
         if (request != null) {
           request.getMetricsTracker().markFailure();
         }
         // need to set writeFuture as failed in case writes have started or chunks have been
         // queued.
         if (!writeFuture.isDone()) {
           writeFuture.setFailure(exception);
         }
         if (!maybeSendErrorResponse(exception)) {
           completeRequest(true);
         }
       }
     } else if (exception != null) {
       // this is probably an attempt to force close the channel *after* the response is already
       // complete.
       log(exception);
       if (!writeFuture.isDone()) {
         writeFuture.setFailure(exception);
       }
       completeRequest(true);
     }
     long responseFinishProcessingTime = System.currentTimeMillis() - responseCompleteStartTime;
     nettyMetrics.responseFinishProcessingTimeInMs.update(responseFinishProcessingTime);
     if (request != null) {
       request
           .getMetricsTracker()
           .nioMetricsTracker
           .addToResponseProcessingTime(responseFinishProcessingTime);
     }
   } catch (Exception e) {
     logger.error("Swallowing exception encountered during onResponseComplete tasks", e);
     nettyMetrics.responseCompleteTasksError.inc();
     if (!writeFuture.isDone()) {
       writeFuture.setFailure(exception);
     }
     completeRequest(true);
   }
 }
  @Override
  public Future<Long> write(ByteBuffer src, Callback<Long> callback) {
    long writeProcessingStartTime = System.currentTimeMillis();
    if (!responseMetadataWritten.get()) {
      maybeWriteResponseMetadata(responseMetadata, responseMetadataWriteListener);
    }
    Chunk chunk = new Chunk(src, callback);
    chunksToWriteCount.incrementAndGet();
    chunksToWrite.add(chunk);
    if (!isOpen()) {
      // the isOpen() check is not before addition to the queue because chunks need to be
      // acknowledged in the order
      // they were received. If we don't add it to the queue and clean up, chunks may be
      // acknowledged out of order.
      logger.debug("Scheduling a chunk cleanup on channel {}", ctx.channel());
      writeFuture.addListener(cleanupCallback);
    } else {
      chunkedWriteHandler.resumeTransfer();
    }

    long writeProcessingTime = System.currentTimeMillis() - writeProcessingStartTime;
    nettyMetrics.writeProcessingTimeInMs.update(writeProcessingTime);
    if (request != null) {
      request
          .getMetricsTracker()
          .nioMetricsTracker
          .addToResponseProcessingTime(writeProcessingTime);
    }
    return chunk.future;
  }
 /**
  * Logs the exception at the appropriate level.
  *
  * @param exception the {@link Exception} that has to be logged.
  */
 private void log(Exception exception) {
   String uri = "unknown";
   RestMethod restMethod = RestMethod.UNKNOWN;
   if (request != null) {
     uri = request.getUri();
     restMethod = request.getRestMethod();
   }
   if (exception instanceof RestServiceException) {
     RestServiceErrorCode errorCode = ((RestServiceException) exception).getErrorCode();
     ResponseStatus responseStatus = ResponseStatus.getResponseStatus(errorCode);
     if (responseStatus == ResponseStatus.InternalServerError) {
       logger.error(
           "Internal error handling request {} with method {}.", uri, restMethod, exception);
     } else {
       logger.trace("Error handling request {} with method {}.", uri, restMethod, exception);
     }
   } else {
     logger.error(
         "Unexpected error handling request {} with method {}.", uri, restMethod, exception);
   }
 }
 /**
  * Sets the request whose response is being served through this instance of NettyResponseChannel.
  *
  * @param request the {@link NettyRequest} whose response is being served through this instance of
  *     NettyResponseChannel.
  */
 protected void setRequest(NettyRequest request) {
   if (request != null) {
     if (this.request == null) {
       this.request = request;
       HttpHeaders.setKeepAlive(responseMetadata, request.isKeepAlive());
     } else {
       throw new IllegalStateException(
           "Request has already been set inside NettyResponseChannel for channel {} "
               + ctx.channel());
     }
   } else {
     throw new IllegalArgumentException("RestRequest provided is null");
   }
 }
 /**
  * Provided a cause, returns an error response with the right status and error message.
  *
  * @param cause the cause of the error.
  * @return a {@link FullHttpResponse} with the error message that can be sent to the client.
  */
 private FullHttpResponse getErrorResponse(Throwable cause) {
   HttpResponseStatus status;
   StringBuilder errReason = new StringBuilder();
   if (cause instanceof RestServiceException) {
     RestServiceErrorCode restServiceErrorCode = ((RestServiceException) cause).getErrorCode();
     status = getHttpResponseStatus(ResponseStatus.getResponseStatus(restServiceErrorCode));
     if (status == HttpResponseStatus.BAD_REQUEST) {
       errReason.append(" [").append(Utils.getRootCause(cause).getMessage()).append("]");
     }
   } else {
     nettyMetrics.internalServerErrorCount.inc();
     status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
   }
   String fullMsg = "Failure: " + status + errReason;
   logger.trace("Constructed error response for the client - [{}]", fullMsg);
   FullHttpResponse response;
   if (request != null && !request.getRestMethod().equals(RestMethod.HEAD)) {
     response =
         new DefaultFullHttpResponse(
             HttpVersion.HTTP_1_1, status, Unpooled.wrappedBuffer(fullMsg.getBytes()));
   } else {
     // for HEAD, we cannot send the actual body but we need to return what the length would have
     // been if this was GET.
     // https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html (Section 9.4)
     response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);
   }
   HttpHeaders.setDate(response, new GregorianCalendar().getTime());
   HttpHeaders.setContentLength(response, fullMsg.length());
   HttpHeaders.setHeader(response, HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8");
   boolean keepAlive =
       request != null
           && !request.getRestMethod().equals(RestMethod.POST)
           && !CLOSE_CONNECTION_ERROR_STATUSES.contains(status);
   HttpHeaders.setKeepAlive(response, keepAlive);
   return response;
 }
 /** Closes the request associated with this NettyResponseChannel. */
 private void closeRequest() {
   if (request != null && request.isOpen()) {
     request.getMetricsTracker().nioMetricsTracker.markRequestCompleted();
     request.close();
   }
 }