All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.http.nio.protocol.AsyncNHttpServiceHandler Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 * ====================================================================
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * .
 *
 */

package org.apache.http.nio.protocol;

import java.io.IOException;

import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseFactory;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.MethodNotSupportedException;
import org.apache.http.ProtocolException;
import org.apache.http.ProtocolVersion;
import org.apache.http.UnsupportedHttpVersionException;
import org.apache.http.annotation.ThreadingBehavior;
import org.apache.http.annotation.Contract;
import org.apache.http.nio.ContentDecoder;
import org.apache.http.nio.ContentEncoder;
import org.apache.http.nio.IOControl;
import org.apache.http.nio.NHttpServerConnection;
import org.apache.http.nio.NHttpServiceHandler;
import org.apache.http.nio.entity.ConsumingNHttpEntity;
import org.apache.http.nio.entity.NByteArrayEntity;
import org.apache.http.nio.entity.NHttpEntityWrapper;
import org.apache.http.nio.entity.ProducingNHttpEntity;
import org.apache.http.nio.util.ByteBufferAllocator;
import org.apache.http.nio.util.HeapByteBufferAllocator;
import org.apache.http.params.DefaultedHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpExpectationVerifier;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.util.Args;
import org.apache.http.util.Asserts;
import org.apache.http.util.EncodingUtils;

/**
 * Fully asynchronous HTTP server side protocol handler implementation that
 * implements the essential requirements of the HTTP protocol for the server
 * side message processing as described by RFC 2616. It is capable of processing
 * HTTP requests with nearly constant memory footprint. Only HTTP message heads
 * are stored in memory, while content of message bodies is streamed directly
 * from the entity to the underlying channel (and vice versa)
 * {@link ConsumingNHttpEntity} and {@link ProducingNHttpEntity} interfaces.
 * 

* When using this class, it is important to ensure that entities supplied for * writing implement {@link ProducingNHttpEntity}. Doing so will allow the * entity to be written out asynchronously. If entities supplied for writing do * not implement {@link ProducingNHttpEntity}, a delegate is added that buffers * the entire contents in memory. Additionally, the buffering might take place * in the I/O thread, which could cause I/O to block temporarily. For best * results, ensure that all entities set on {@link HttpResponse}s from * {@link NHttpRequestHandler}s implement {@link ProducingNHttpEntity}. *

* If incoming requests enclose a content entity, {@link NHttpRequestHandler}s * are expected to return a {@link ConsumingNHttpEntity} for reading the * content. After the entity is finished reading the data, * {@link NHttpRequestHandler#handle(HttpRequest, HttpResponse, NHttpResponseTrigger, HttpContext)} * is called to generate a response. *

* Individual {@link NHttpRequestHandler}s do not have to submit a response * immediately. They can defer transmission of the HTTP response back to the * client without blocking the I/O thread and to delegate the processing the * HTTP request to a worker thread. The worker thread in its turn can use an * instance of {@link NHttpResponseTrigger} passed as a parameter to submit * a response as at a later point of time once the response becomes available. * * @since 4.0 * * @deprecated (4.2) use {@link HttpAsyncService} */ @Deprecated @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class AsyncNHttpServiceHandler extends NHttpHandlerBase implements NHttpServiceHandler { protected final HttpResponseFactory responseFactory; protected NHttpRequestHandlerResolver handlerResolver; protected HttpExpectationVerifier expectationVerifier; public AsyncNHttpServiceHandler( final HttpProcessor httpProcessor, final HttpResponseFactory responseFactory, final ConnectionReuseStrategy connStrategy, final ByteBufferAllocator allocator, final HttpParams params) { super(httpProcessor, connStrategy, allocator, params); Args.notNull(responseFactory, "Response factory"); this.responseFactory = responseFactory; } public AsyncNHttpServiceHandler( final HttpProcessor httpProcessor, final HttpResponseFactory responseFactory, final ConnectionReuseStrategy connStrategy, final HttpParams params) { this(httpProcessor, responseFactory, connStrategy, HeapByteBufferAllocator.INSTANCE, params); } public void setExpectationVerifier(final HttpExpectationVerifier expectationVerifier) { this.expectationVerifier = expectationVerifier; } public void setHandlerResolver(final NHttpRequestHandlerResolver handlerResolver) { this.handlerResolver = handlerResolver; } @Override public void connected(final NHttpServerConnection conn) { final HttpContext context = conn.getContext(); final ServerConnState connState = new ServerConnState(); context.setAttribute(CONN_STATE, connState); context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn); if (this.eventListener != null) { this.eventListener.connectionOpen(conn); } } @Override public void requestReceived(final NHttpServerConnection conn) { final HttpContext context = conn.getContext(); final ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE); final HttpRequest request = conn.getHttpRequest(); request.setParams(new DefaultedHttpParams(request.getParams(), this.params)); connState.setRequest(request); final NHttpRequestHandler requestHandler = getRequestHandler(request); connState.setRequestHandler(requestHandler); ProtocolVersion ver = request.getRequestLine().getProtocolVersion(); if (!ver.lessEquals(HttpVersion.HTTP_1_1)) { // Downgrade protocol version if greater than HTTP/1.1 ver = HttpVersion.HTTP_1_1; } HttpResponse response; try { if (request instanceof HttpEntityEnclosingRequest) { final HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request; if (entityRequest.expectContinue()) { response = this.responseFactory.newHttpResponse( ver, HttpStatus.SC_CONTINUE, context); response.setParams( new DefaultedHttpParams(response.getParams(), this.params)); if (this.expectationVerifier != null) { try { this.expectationVerifier.verify(request, response, context); } catch (final HttpException ex) { response = this.responseFactory.newHttpResponse( HttpVersion.HTTP_1_0, HttpStatus.SC_INTERNAL_SERVER_ERROR, context); response.setParams( new DefaultedHttpParams(response.getParams(), this.params)); handleException(ex, response); } } if (response.getStatusLine().getStatusCode() < 200) { // Send 1xx response indicating the server expections // have been met conn.submitResponse(response); } else { conn.resetInput(); sendResponse(conn, request, response); } } // Request content is expected. ConsumingNHttpEntity consumingEntity = null; // Lookup request handler for this request if (requestHandler != null) { consumingEntity = requestHandler.entityRequest(entityRequest, context); } if (consumingEntity == null) { consumingEntity = new NullNHttpEntity(entityRequest.getEntity()); } entityRequest.setEntity(consumingEntity); connState.setConsumingEntity(consumingEntity); } else { // No request content is expected. // Process request right away conn.suspendInput(); processRequest(conn, request); } } catch (final IOException ex) { shutdownConnection(conn, ex); if (this.eventListener != null) { this.eventListener.fatalIOException(ex, conn); } } catch (final HttpException ex) { closeConnection(conn, ex); if (this.eventListener != null) { this.eventListener.fatalProtocolException(ex, conn); } } } @Override public void closed(final NHttpServerConnection conn) { final HttpContext context = conn.getContext(); final ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE); try { connState.reset(); } catch (final IOException ex) { if (this.eventListener != null) { this.eventListener.fatalIOException(ex, conn); } } if (this.eventListener != null) { this.eventListener.connectionClosed(conn); } } @Override public void exception(final NHttpServerConnection conn, final HttpException httpex) { if (conn.isResponseSubmitted()) { // There is not much that we can do if a response head // has already been submitted closeConnection(conn, httpex); if (eventListener != null) { eventListener.fatalProtocolException(httpex, conn); } return; } final HttpContext context = conn.getContext(); try { final HttpResponse response = this.responseFactory.newHttpResponse( HttpVersion.HTTP_1_0, HttpStatus.SC_INTERNAL_SERVER_ERROR, context); response.setParams( new DefaultedHttpParams(response.getParams(), this.params)); handleException(httpex, response); response.setEntity(null); sendResponse(conn, null, response); } catch (final IOException ex) { shutdownConnection(conn, ex); if (this.eventListener != null) { this.eventListener.fatalIOException(ex, conn); } } catch (final HttpException ex) { closeConnection(conn, ex); if (this.eventListener != null) { this.eventListener.fatalProtocolException(ex, conn); } } } @Override public void exception(final NHttpServerConnection conn, final IOException ex) { shutdownConnection(conn, ex); if (this.eventListener != null) { this.eventListener.fatalIOException(ex, conn); } } @Override public void timeout(final NHttpServerConnection conn) { handleTimeout(conn); } @Override public void inputReady(final NHttpServerConnection conn, final ContentDecoder decoder) { final HttpContext context = conn.getContext(); final ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE); final HttpRequest request = connState.getRequest(); final ConsumingNHttpEntity consumingEntity = connState.getConsumingEntity(); try { consumingEntity.consumeContent(decoder, conn); if (decoder.isCompleted()) { conn.suspendInput(); processRequest(conn, request); } } catch (final IOException ex) { shutdownConnection(conn, ex); if (this.eventListener != null) { this.eventListener.fatalIOException(ex, conn); } } catch (final HttpException ex) { closeConnection(conn, ex); if (this.eventListener != null) { this.eventListener.fatalProtocolException(ex, conn); } } } @Override public void responseReady(final NHttpServerConnection conn) { final HttpContext context = conn.getContext(); final ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE); if (connState.isHandled()) { return; } final HttpRequest request = connState.getRequest(); try { final IOException ioex = connState.getIOException(); if (ioex != null) { throw ioex; } final HttpException httpex = connState.getHttpException(); if (httpex != null) { final HttpResponse response = this.responseFactory.newHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_INTERNAL_SERVER_ERROR, context); response.setParams( new DefaultedHttpParams(response.getParams(), this.params)); handleException(httpex, response); connState.setResponse(response); } final HttpResponse response = connState.getResponse(); if (response != null) { connState.setHandled(true); sendResponse(conn, request, response); } } catch (final IOException ex) { shutdownConnection(conn, ex); if (this.eventListener != null) { this.eventListener.fatalIOException(ex, conn); } } catch (final HttpException ex) { closeConnection(conn, ex); if (this.eventListener != null) { this.eventListener.fatalProtocolException(ex, conn); } } } @Override public void outputReady(final NHttpServerConnection conn, final ContentEncoder encoder) { final HttpContext context = conn.getContext(); final ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE); final HttpResponse response = conn.getHttpResponse(); try { final ProducingNHttpEntity entity = connState.getProducingEntity(); entity.produceContent(encoder, conn); if (encoder.isCompleted()) { connState.finishOutput(); if (!this.connStrategy.keepAlive(response, context)) { conn.close(); } else { // Ready to process new request connState.reset(); conn.requestInput(); } responseComplete(response, context); } } catch (final IOException ex) { shutdownConnection(conn, ex); if (this.eventListener != null) { this.eventListener.fatalIOException(ex, conn); } } } private void handleException(final HttpException ex, final HttpResponse response) { int code = HttpStatus.SC_INTERNAL_SERVER_ERROR; if (ex instanceof MethodNotSupportedException) { code = HttpStatus.SC_NOT_IMPLEMENTED; } else if (ex instanceof UnsupportedHttpVersionException) { code = HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED; } else if (ex instanceof ProtocolException) { code = HttpStatus.SC_BAD_REQUEST; } response.setStatusCode(code); final byte[] msg = EncodingUtils.getAsciiBytes(ex.getMessage()); final NByteArrayEntity entity = new NByteArrayEntity(msg); entity.setContentType("text/plain; charset=US-ASCII"); response.setEntity(entity); } /** * @throws HttpException - not thrown currently */ private void processRequest( final NHttpServerConnection conn, final HttpRequest request) throws IOException, HttpException { final HttpContext context = conn.getContext(); final ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE); ProtocolVersion ver = request.getRequestLine().getProtocolVersion(); if (!ver.lessEquals(HttpVersion.HTTP_1_1)) { // Downgrade protocol version if greater than HTTP/1.1 ver = HttpVersion.HTTP_1_1; } final NHttpResponseTrigger trigger = new ResponseTriggerImpl(connState, conn); try { this.httpProcessor.process(request, context); final NHttpRequestHandler handler = connState.getRequestHandler(); if (handler != null) { final HttpResponse response = this.responseFactory.newHttpResponse( ver, HttpStatus.SC_OK, context); response.setParams( new DefaultedHttpParams(response.getParams(), this.params)); handler.handle( request, response, trigger, context); } else { final HttpResponse response = this.responseFactory.newHttpResponse(ver, HttpStatus.SC_NOT_IMPLEMENTED, context); response.setParams( new DefaultedHttpParams(response.getParams(), this.params)); trigger.submitResponse(response); } } catch (final HttpException ex) { trigger.handleException(ex); } } private void sendResponse( final NHttpServerConnection conn, final HttpRequest request, final HttpResponse response) throws IOException, HttpException { final HttpContext context = conn.getContext(); final ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE); // Now that a response is ready, we can cleanup the listener for the request. connState.finishInput(); // Some processers need the request that generated this response. context.setAttribute(ExecutionContext.HTTP_REQUEST, request); this.httpProcessor.process(response, context); context.setAttribute(ExecutionContext.HTTP_REQUEST, null); if (response.getEntity() != null && !canResponseHaveBody(request, response)) { response.setEntity(null); } final HttpEntity entity = response.getEntity(); if (entity != null) { if (entity instanceof ProducingNHttpEntity) { connState.setProducingEntity((ProducingNHttpEntity) entity); } else { connState.setProducingEntity(new NHttpEntityWrapper(entity)); } } conn.submitResponse(response); if (entity == null) { if (!this.connStrategy.keepAlive(response, context)) { conn.close(); } else { // Ready to process new request connState.reset(); conn.requestInput(); } responseComplete(response, context); } } /** * Signals that this response has been fully sent. This will be called after * submitting the response to a connection, if there is no entity in the * response. If there is an entity, it will be called after the entity has * completed. */ protected void responseComplete(final HttpResponse response, final HttpContext context) { } private NHttpRequestHandler getRequestHandler(final HttpRequest request) { NHttpRequestHandler handler = null; if (this.handlerResolver != null) { final String requestURI = request.getRequestLine().getUri(); handler = this.handlerResolver.lookup(requestURI); } return handler; } protected static class ServerConnState { private volatile NHttpRequestHandler requestHandler; private volatile HttpRequest request; private volatile ConsumingNHttpEntity consumingEntity; private volatile HttpResponse response; private volatile ProducingNHttpEntity producingEntity; private volatile IOException ioex; private volatile HttpException httpex; private volatile boolean handled; public void finishInput() throws IOException { if (this.consumingEntity != null) { this.consumingEntity.finish(); this.consumingEntity = null; } } public void finishOutput() throws IOException { if (this.producingEntity != null) { this.producingEntity.finish(); this.producingEntity = null; } } public void reset() throws IOException { finishInput(); this.request = null; finishOutput(); this.handled = false; this.response = null; this.ioex = null; this.httpex = null; this.requestHandler = null; } public NHttpRequestHandler getRequestHandler() { return this.requestHandler; } public void setRequestHandler(final NHttpRequestHandler requestHandler) { this.requestHandler = requestHandler; } public HttpRequest getRequest() { return this.request; } public void setRequest(final HttpRequest request) { this.request = request; } public ConsumingNHttpEntity getConsumingEntity() { return this.consumingEntity; } public void setConsumingEntity(final ConsumingNHttpEntity consumingEntity) { this.consumingEntity = consumingEntity; } public HttpResponse getResponse() { return this.response; } public void setResponse(final HttpResponse response) { this.response = response; } public ProducingNHttpEntity getProducingEntity() { return this.producingEntity; } public void setProducingEntity(final ProducingNHttpEntity producingEntity) { this.producingEntity = producingEntity; } public IOException getIOException() { return this.ioex; } public IOException getIOExepction() { return this.ioex; } public void setIOException(final IOException ex) { this.ioex = ex; } public void setIOExepction(final IOException ex) { this.ioex = ex; } public HttpException getHttpException() { return this.httpex; } public HttpException getHttpExepction() { return this.httpex; } public void setHttpException(final HttpException ex) { this.httpex = ex; } public void setHttpExepction(final HttpException ex) { this.httpex = ex; } public boolean isHandled() { return this.handled; } public void setHandled(final boolean handled) { this.handled = handled; } } private static class ResponseTriggerImpl implements NHttpResponseTrigger { private final ServerConnState connState; private final IOControl iocontrol; private volatile boolean triggered; public ResponseTriggerImpl(final ServerConnState connState, final IOControl iocontrol) { super(); this.connState = connState; this.iocontrol = iocontrol; } @Override public void submitResponse(final HttpResponse response) { Args.notNull(response, "Response"); Asserts.check(!this.triggered, "Response already triggered"); this.triggered = true; this.connState.setResponse(response); this.iocontrol.requestOutput(); } @Override public void handleException(final HttpException ex) { Asserts.check(!this.triggered, "Response already triggered"); this.triggered = true; this.connState.setHttpException(ex); this.iocontrol.requestOutput(); } @Override public void handleException(final IOException ex) { Asserts.check(!this.triggered, "Response already triggered"); this.triggered = true; this.connState.setIOException(ex); this.iocontrol.requestOutput(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy