@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(); } }