2
* $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/httpcore/tags/4.0.1/httpcore-nio/src/main/java/org/apache/http/nio/protocol/AsyncNHttpServiceHandler.java $
4
* $Date: 2009-02-26 04:03:22 +0100 (Thu, 26 Feb 2009) $
6
* ====================================================================
7
* Licensed to the Apache Software Foundation (ASF) under one
8
* or more contributor license agreements. See the NOTICE file
9
* distributed with this work for additional information
10
* regarding copyright ownership. The ASF licenses this file
11
* to you under the Apache License, Version 2.0 (the
12
* "License"); you may not use this file except in compliance
13
* with the License. You may obtain a copy of the License at
15
* http://www.apache.org/licenses/LICENSE-2.0
17
* Unless required by applicable law or agreed to in writing,
18
* software distributed under the License is distributed on an
19
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20
* KIND, either express or implied. See the License for the
21
* specific language governing permissions and limitations
23
* ====================================================================
25
* This software consists of voluntary contributions made by many
26
* individuals on behalf of the Apache Software Foundation. For more
27
* information on the Apache Software Foundation, please see
28
* <http://www.apache.org/>.
32
package org.apache.http.nio.protocol;
34
import java.io.IOException;
36
import org.apache.http.ConnectionReuseStrategy;
37
import org.apache.http.HttpEntity;
38
import org.apache.http.HttpEntityEnclosingRequest;
39
import org.apache.http.HttpException;
40
import org.apache.http.HttpRequest;
41
import org.apache.http.HttpResponse;
42
import org.apache.http.HttpResponseFactory;
43
import org.apache.http.HttpStatus;
44
import org.apache.http.HttpVersion;
45
import org.apache.http.MethodNotSupportedException;
46
import org.apache.http.ProtocolException;
47
import org.apache.http.ProtocolVersion;
48
import org.apache.http.UnsupportedHttpVersionException;
49
import org.apache.http.nio.ContentDecoder;
50
import org.apache.http.nio.ContentEncoder;
51
import org.apache.http.nio.IOControl;
52
import org.apache.http.nio.NHttpServerConnection;
53
import org.apache.http.nio.NHttpServiceHandler;
54
import org.apache.http.nio.entity.ConsumingNHttpEntity;
55
import org.apache.http.nio.entity.ConsumingNHttpEntityTemplate;
56
import org.apache.http.nio.entity.NByteArrayEntity;
57
import org.apache.http.nio.entity.NHttpEntityWrapper;
58
import org.apache.http.nio.entity.ProducingNHttpEntity;
59
import org.apache.http.nio.entity.SkipContentListener;
60
import org.apache.http.nio.util.ByteBufferAllocator;
61
import org.apache.http.nio.util.HeapByteBufferAllocator;
62
import org.apache.http.params.DefaultedHttpParams;
63
import org.apache.http.params.HttpParams;
64
import org.apache.http.protocol.ExecutionContext;
65
import org.apache.http.protocol.HttpContext;
66
import org.apache.http.protocol.HttpExpectationVerifier;
67
import org.apache.http.protocol.HttpProcessor;
68
import org.apache.http.util.EncodingUtils;
71
* Fully asynchronous HTTP server side protocol handler implementation that
72
* implements the essential requirements of the HTTP protocol for the server
73
* side message processing as described by RFC 2616. It is capable of processing
74
* HTTP requests with nearly constant memory footprint. Only HTTP message heads
75
* are stored in memory, while content of message bodies is streamed directly
76
* from the entity to the underlying channel (and vice versa)
77
* {@link ConsumingNHttpEntity} and {@link ProducingNHttpEntity} interfaces.
79
* When using this class, it is important to ensure that entities supplied for
80
* writing implement {@link ProducingNHttpEntity}. Doing so will allow the
81
* entity to be written out asynchronously. If entities supplied for writing do
82
* not implement {@link ProducingNHttpEntity}, a delegate is added that buffers
83
* the entire contents in memory. Additionally, the buffering might take place
84
* in the I/O thread, which could cause I/O to block temporarily. For best
85
* results, ensure that all entities set on {@link HttpResponse}s from
86
* {@link NHttpRequestHandler}s implement {@link ProducingNHttpEntity}.
88
* If incoming requests enclose a content entity, {@link NHttpRequestHandler}s
89
* are expected to return a {@link ConsumingNHttpEntity} for reading the
90
* content. After the entity is finished reading the data,
91
* {@link NHttpRequestHandler#handle(HttpRequest, HttpResponse, NHttpResponseTrigger, HttpContext)}
92
* is called to generate a response.
94
* Individual {@link NHttpRequestHandler}s do not have to submit a response
95
* immediately. They can defer transmission of the HTTP response back to the
96
* client without blocking the I/O thread and to delegate the processing the
97
* HTTP request to a worker thread. The worker thread in its turn can use an
98
* instance of {@link NHttpResponseTrigger} passed as a parameter to submit
99
* a response as at a later point of time once the response becomes available.
101
* @see ConsumingNHttpEntity
102
* @see ProducingNHttpEntity
106
public class AsyncNHttpServiceHandler extends NHttpHandlerBase
107
implements NHttpServiceHandler {
109
protected final HttpResponseFactory responseFactory;
111
protected NHttpRequestHandlerResolver handlerResolver;
112
protected HttpExpectationVerifier expectationVerifier;
114
public AsyncNHttpServiceHandler(
115
final HttpProcessor httpProcessor,
116
final HttpResponseFactory responseFactory,
117
final ConnectionReuseStrategy connStrategy,
118
final ByteBufferAllocator allocator,
119
final HttpParams params) {
120
super(httpProcessor, connStrategy, allocator, params);
121
if (responseFactory == null) {
122
throw new IllegalArgumentException("Response factory may not be null");
124
this.responseFactory = responseFactory;
127
public AsyncNHttpServiceHandler(
128
final HttpProcessor httpProcessor,
129
final HttpResponseFactory responseFactory,
130
final ConnectionReuseStrategy connStrategy,
131
final HttpParams params) {
132
this(httpProcessor, responseFactory, connStrategy,
133
new HeapByteBufferAllocator(), params);
136
public void setExpectationVerifier(final HttpExpectationVerifier expectationVerifier) {
137
this.expectationVerifier = expectationVerifier;
140
public void setHandlerResolver(final NHttpRequestHandlerResolver handlerResolver) {
141
this.handlerResolver = handlerResolver;
144
public void connected(final NHttpServerConnection conn) {
145
HttpContext context = conn.getContext();
147
ServerConnState connState = new ServerConnState();
148
context.setAttribute(CONN_STATE, connState);
149
context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
151
if (this.eventListener != null) {
152
this.eventListener.connectionOpen(conn);
156
public void requestReceived(final NHttpServerConnection conn) {
157
HttpContext context = conn.getContext();
159
ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
161
HttpRequest request = conn.getHttpRequest();
162
request.setParams(new DefaultedHttpParams(request.getParams(), this.params));
164
connState.setRequest(request);
166
NHttpRequestHandler requestHandler = getRequestHandler(request);
167
connState.setRequestHandler(requestHandler);
169
ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
170
if (!ver.lessEquals(HttpVersion.HTTP_1_1)) {
171
// Downgrade protocol version if greater than HTTP/1.1
172
ver = HttpVersion.HTTP_1_1;
175
HttpResponse response;
179
if (request instanceof HttpEntityEnclosingRequest) {
180
if (((HttpEntityEnclosingRequest) request).expectContinue()) {
181
response = this.responseFactory.newHttpResponse(
182
ver, HttpStatus.SC_CONTINUE, context);
184
new DefaultedHttpParams(response.getParams(), this.params));
186
if (this.expectationVerifier != null) {
188
this.expectationVerifier.verify(request, response, context);
189
} catch (HttpException ex) {
190
response = this.responseFactory.newHttpResponse(
191
HttpVersion.HTTP_1_0,
192
HttpStatus.SC_INTERNAL_SERVER_ERROR,
195
new DefaultedHttpParams(response.getParams(), this.params));
196
handleException(ex, response);
200
if (response.getStatusLine().getStatusCode() < 200) {
201
// Send 1xx response indicating the server expections
203
conn.submitResponse(response);
206
sendResponse(conn, request, response);
209
// Request content is expected.
210
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
212
// Lookup request handler for this request
213
if (requestHandler != null) {
214
ConsumingNHttpEntity consumingEntity = requestHandler.entityRequest(
215
(HttpEntityEnclosingRequest) request, context);
216
if (consumingEntity == null) {
217
consumingEntity = new ConsumingNHttpEntityTemplate(
219
new SkipContentListener(this.allocator));
221
((HttpEntityEnclosingRequest) request).setEntity(consumingEntity);
222
connState.setConsumingEntity(consumingEntity);
226
// No request content is expected.
227
// Process request right away
229
processRequest(conn, request);
232
} catch (IOException ex) {
233
shutdownConnection(conn, ex);
234
if (this.eventListener != null) {
235
this.eventListener.fatalIOException(ex, conn);
237
} catch (HttpException ex) {
238
closeConnection(conn, ex);
239
if (this.eventListener != null) {
240
this.eventListener.fatalProtocolException(ex, conn);
246
public void closed(final NHttpServerConnection conn) {
247
HttpContext context = conn.getContext();
249
ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
252
} catch (IOException ex) {
253
if (this.eventListener != null) {
254
this.eventListener.fatalIOException(ex, conn);
257
if (this.eventListener != null) {
258
this.eventListener.connectionClosed(conn);
262
public void exception(final NHttpServerConnection conn, final HttpException httpex) {
263
if (conn.isResponseSubmitted()) {
264
// There is not much that we can do if a response head
265
// has already been submitted
266
closeConnection(conn, httpex);
267
if (eventListener != null) {
268
eventListener.fatalProtocolException(httpex, conn);
273
HttpContext context = conn.getContext();
275
HttpResponse response = this.responseFactory.newHttpResponse(
276
HttpVersion.HTTP_1_0, HttpStatus.SC_INTERNAL_SERVER_ERROR, context);
278
new DefaultedHttpParams(response.getParams(), this.params));
279
handleException(httpex, response);
280
response.setEntity(null);
281
sendResponse(conn, null, response);
283
} catch (IOException ex) {
284
shutdownConnection(conn, ex);
285
if (this.eventListener != null) {
286
this.eventListener.fatalIOException(ex, conn);
288
} catch (HttpException ex) {
289
closeConnection(conn, ex);
290
if (this.eventListener != null) {
291
this.eventListener.fatalProtocolException(ex, conn);
296
public void exception(final NHttpServerConnection conn, final IOException ex) {
297
shutdownConnection(conn, ex);
299
if (this.eventListener != null) {
300
this.eventListener.fatalIOException(ex, conn);
304
public void timeout(final NHttpServerConnection conn) {
308
public void inputReady(final NHttpServerConnection conn, final ContentDecoder decoder) {
309
HttpContext context = conn.getContext();
310
ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
312
HttpRequest request = connState.getRequest();
313
ConsumingNHttpEntity consumingEntity = connState.getConsumingEntity();
317
consumingEntity.consumeContent(decoder, conn);
318
if (decoder.isCompleted()) {
320
processRequest(conn, request);
323
} catch (IOException ex) {
324
shutdownConnection(conn, ex);
325
if (this.eventListener != null) {
326
this.eventListener.fatalIOException(ex, conn);
328
} catch (HttpException ex) {
329
closeConnection(conn, ex);
330
if (this.eventListener != null) {
331
this.eventListener.fatalProtocolException(ex, conn);
336
public void responseReady(final NHttpServerConnection conn) {
337
HttpContext context = conn.getContext();
338
ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
340
if (connState.isHandled()) {
344
HttpRequest request = connState.getRequest();
348
IOException ioex = connState.getIOExepction();
353
HttpException httpex = connState.getHttpExepction();
354
if (httpex != null) {
355
HttpResponse response = this.responseFactory.newHttpResponse(HttpVersion.HTTP_1_0,
356
HttpStatus.SC_INTERNAL_SERVER_ERROR, context);
358
new DefaultedHttpParams(response.getParams(), this.params));
359
handleException(httpex, response);
360
connState.setResponse(response);
363
HttpResponse response = connState.getResponse();
364
if (response != null) {
365
connState.setHandled(true);
366
sendResponse(conn, request, response);
369
} catch (IOException ex) {
370
shutdownConnection(conn, ex);
371
if (this.eventListener != null) {
372
this.eventListener.fatalIOException(ex, conn);
374
} catch (HttpException ex) {
375
closeConnection(conn, ex);
376
if (this.eventListener != null) {
377
this.eventListener.fatalProtocolException(ex, conn);
382
public void outputReady(final NHttpServerConnection conn, final ContentEncoder encoder) {
383
HttpContext context = conn.getContext();
384
ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
386
HttpResponse response = conn.getHttpResponse();
389
ProducingNHttpEntity entity = connState.getProducingEntity();
390
entity.produceContent(encoder, conn);
392
if (encoder.isCompleted()) {
393
connState.finishOutput();
394
if (!this.connStrategy.keepAlive(response, context)) {
397
// Ready to process new request
401
responseComplete(response, context);
404
} catch (IOException ex) {
405
shutdownConnection(conn, ex);
406
if (this.eventListener != null) {
407
this.eventListener.fatalIOException(ex, conn);
412
private void handleException(final HttpException ex, final HttpResponse response) {
413
int code = HttpStatus.SC_INTERNAL_SERVER_ERROR;
414
if (ex instanceof MethodNotSupportedException) {
415
code = HttpStatus.SC_NOT_IMPLEMENTED;
416
} else if (ex instanceof UnsupportedHttpVersionException) {
417
code = HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED;
418
} else if (ex instanceof ProtocolException) {
419
code = HttpStatus.SC_BAD_REQUEST;
421
response.setStatusCode(code);
423
byte[] msg = EncodingUtils.getAsciiBytes(ex.getMessage());
424
NByteArrayEntity entity = new NByteArrayEntity(msg);
425
entity.setContentType("text/plain; charset=US-ASCII");
426
response.setEntity(entity);
430
* @throws HttpException - not thrown currently
432
private void processRequest(
433
final NHttpServerConnection conn,
434
final HttpRequest request) throws IOException, HttpException {
436
HttpContext context = conn.getContext();
437
ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
439
ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
441
if (!ver.lessEquals(HttpVersion.HTTP_1_1)) {
442
// Downgrade protocol version if greater than HTTP/1.1
443
ver = HttpVersion.HTTP_1_1;
446
NHttpResponseTrigger trigger = new ResponseTriggerImpl(connState, conn);
448
this.httpProcessor.process(request, context);
450
NHttpRequestHandler handler = connState.getRequestHandler();
451
if (handler != null) {
452
HttpResponse response = this.responseFactory.newHttpResponse(
453
ver, HttpStatus.SC_OK, context);
455
new DefaultedHttpParams(response.getParams(), this.params));
463
HttpResponse response = this.responseFactory.newHttpResponse(ver,
464
HttpStatus.SC_NOT_IMPLEMENTED, context);
466
new DefaultedHttpParams(response.getParams(), this.params));
467
trigger.submitResponse(response);
470
} catch (HttpException ex) {
471
trigger.handleException(ex);
475
private void sendResponse(
476
final NHttpServerConnection conn,
477
final HttpRequest request,
478
final HttpResponse response) throws IOException, HttpException {
479
HttpContext context = conn.getContext();
480
ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
482
// Now that a response is ready, we can cleanup the listener for the request.
483
connState.finishInput();
485
// Some processers need the request that generated this response.
486
context.setAttribute(ExecutionContext.HTTP_REQUEST, request);
487
this.httpProcessor.process(response, context);
488
context.setAttribute(ExecutionContext.HTTP_REQUEST, null);
490
if (response.getEntity() != null && !canResponseHaveBody(request, response)) {
491
response.setEntity(null);
494
HttpEntity entity = response.getEntity();
495
if (entity != null) {
496
if (entity instanceof ProducingNHttpEntity) {
497
connState.setProducingEntity((ProducingNHttpEntity) entity);
499
connState.setProducingEntity(new NHttpEntityWrapper(entity));
503
conn.submitResponse(response);
505
if (entity == null) {
506
if (!this.connStrategy.keepAlive(response, context)) {
509
// Ready to process new request
513
responseComplete(response, context);
518
* Signals that this response has been fully sent. This will be called after
519
* submitting the response to a connection, if there is no entity in the
520
* response. If there is an entity, it will be called after the entity has
523
protected void responseComplete(HttpResponse response, HttpContext context) {
526
private NHttpRequestHandler getRequestHandler(HttpRequest request) {
527
NHttpRequestHandler handler = null;
528
if (this.handlerResolver != null) {
529
String requestURI = request.getRequestLine().getUri();
530
handler = this.handlerResolver.lookup(requestURI);
536
protected static class ServerConnState {
538
private volatile NHttpRequestHandler requestHandler;
539
private volatile HttpRequest request;
540
private volatile ConsumingNHttpEntity consumingEntity;
541
private volatile HttpResponse response;
542
private volatile ProducingNHttpEntity producingEntity;
543
private volatile IOException ioex;
544
private volatile HttpException httpex;
545
private volatile boolean handled;
547
public void finishInput() throws IOException {
548
if (this.consumingEntity != null) {
549
this.consumingEntity.finish();
550
this.consumingEntity = null;
554
public void finishOutput() throws IOException {
555
if (this.producingEntity != null) {
556
this.producingEntity.finish();
557
this.producingEntity = null;
561
public void reset() throws IOException {
565
this.handled = false;
566
this.response = null;
569
this.requestHandler = null;
572
public NHttpRequestHandler getRequestHandler() {
573
return this.requestHandler;
576
public void setRequestHandler(final NHttpRequestHandler requestHandler) {
577
this.requestHandler = requestHandler;
580
public HttpRequest getRequest() {
584
public void setRequest(final HttpRequest request) {
585
this.request = request;
588
public ConsumingNHttpEntity getConsumingEntity() {
589
return this.consumingEntity;
592
public void setConsumingEntity(final ConsumingNHttpEntity consumingEntity) {
593
this.consumingEntity = consumingEntity;
596
public HttpResponse getResponse() {
597
return this.response;
600
public void setResponse(final HttpResponse response) {
601
this.response = response;
604
public ProducingNHttpEntity getProducingEntity() {
605
return this.producingEntity;
608
public void setProducingEntity(final ProducingNHttpEntity producingEntity) {
609
this.producingEntity = producingEntity;
612
public IOException getIOExepction() {
616
public void setIOExepction(final IOException ex) {
620
public HttpException getHttpExepction() {
624
public void setHttpExepction(final HttpException ex) {
628
public boolean isHandled() {
632
public void setHandled(boolean handled) {
633
this.handled = handled;
638
private static class ResponseTriggerImpl implements NHttpResponseTrigger {
640
private final ServerConnState connState;
641
private final IOControl iocontrol;
643
private volatile boolean triggered;
645
public ResponseTriggerImpl(final ServerConnState connState, final IOControl iocontrol) {
647
this.connState = connState;
648
this.iocontrol = iocontrol;
651
public void submitResponse(final HttpResponse response) {
652
if (response == null) {
653
throw new IllegalArgumentException("Response may not be null");
655
if (this.triggered) {
656
throw new IllegalStateException("Response already triggered");
658
this.triggered = true;
659
this.connState.setResponse(response);
660
this.iocontrol.requestOutput();
663
public void handleException(final HttpException ex) {
664
if (this.triggered) {
665
throw new IllegalStateException("Response already triggered");
667
this.triggered = true;
668
this.connState.setHttpExepction(ex);
669
this.iocontrol.requestOutput();
672
public void handleException(final IOException ex) {
673
if (this.triggered) {
674
throw new IllegalStateException("Response already triggered");
676
this.triggered = true;
677
this.connState.setIOExepction(ex);
678
this.iocontrol.requestOutput();