View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.nio.protocol;
29  
30  import java.io.IOException;
31  
32  import org.apache.http.ConnectionReuseStrategy;
33  import org.apache.http.HttpEntity;
34  import org.apache.http.HttpEntityEnclosingRequest;
35  import org.apache.http.HttpException;
36  import org.apache.http.HttpRequest;
37  import org.apache.http.HttpResponse;
38  import org.apache.http.HttpResponseFactory;
39  import org.apache.http.HttpStatus;
40  import org.apache.http.HttpVersion;
41  import org.apache.http.MethodNotSupportedException;
42  import org.apache.http.ProtocolException;
43  import org.apache.http.ProtocolVersion;
44  import org.apache.http.UnsupportedHttpVersionException;
45  import org.apache.http.annotation.Immutable;
46  import org.apache.http.nio.ContentDecoder;
47  import org.apache.http.nio.ContentEncoder;
48  import org.apache.http.nio.IOControl;
49  import org.apache.http.nio.NHttpServerConnection;
50  import org.apache.http.nio.NHttpServiceHandler;
51  import org.apache.http.nio.entity.ConsumingNHttpEntity;
52  import org.apache.http.nio.entity.NByteArrayEntity;
53  import org.apache.http.nio.entity.NHttpEntityWrapper;
54  import org.apache.http.nio.entity.ProducingNHttpEntity;
55  import org.apache.http.nio.util.ByteBufferAllocator;
56  import org.apache.http.nio.util.HeapByteBufferAllocator;
57  import org.apache.http.params.DefaultedHttpParams;
58  import org.apache.http.params.HttpParams;
59  import org.apache.http.protocol.ExecutionContext;
60  import org.apache.http.protocol.HttpContext;
61  import org.apache.http.protocol.HttpExpectationVerifier;
62  import org.apache.http.protocol.HttpProcessor;
63  import org.apache.http.util.Args;
64  import org.apache.http.util.Asserts;
65  import org.apache.http.util.EncodingUtils;
66  
67  /**
68   * Fully asynchronous HTTP server side protocol handler implementation that
69   * implements the essential requirements of the HTTP protocol for the server
70   * side message processing as described by RFC 2616. It is capable of processing
71   * HTTP requests with nearly constant memory footprint. Only HTTP message heads
72   * are stored in memory, while content of message bodies is streamed directly
73   * from the entity to the underlying channel (and vice versa)
74   * {@link ConsumingNHttpEntity} and {@link ProducingNHttpEntity} interfaces.
75   * <p>
76   * When using this class, it is important to ensure that entities supplied for
77   * writing implement {@link ProducingNHttpEntity}. Doing so will allow the
78   * entity to be written out asynchronously. If entities supplied for writing do
79   * not implement {@link ProducingNHttpEntity}, a delegate is added that buffers
80   * the entire contents in memory. Additionally, the buffering might take place
81   * in the I/O thread, which could cause I/O to block temporarily. For best
82   * results, ensure that all entities set on {@link HttpResponse}s from
83   * {@link NHttpRequestHandler}s implement {@link ProducingNHttpEntity}.
84   * <p>
85   * If incoming requests enclose a content entity, {@link NHttpRequestHandler}s
86   * are expected to return a {@link ConsumingNHttpEntity} for reading the
87   * content. After the entity is finished reading the data,
88   * {@link NHttpRequestHandler#handle(HttpRequest, HttpResponse, NHttpResponseTrigger, HttpContext)}
89   * is called to generate a response.
90   * <p>
91   * Individual {@link NHttpRequestHandler}s do not have to submit a response
92   * immediately. They can defer transmission of the HTTP response back to the
93   * client without blocking the I/O thread and to delegate the processing the
94   * HTTP request to a worker thread. The worker thread in its turn can use an
95   * instance of {@link NHttpResponseTrigger} passed as a parameter to submit
96   * a response as at a later point of time once the response becomes available.
97   *
98   * @since 4.0
99   *
100  * @deprecated (4.2) use {@link HttpAsyncService}
101  */
102 @Deprecated
103 @Immutable // provided injected dependencies are immutable
104 public class AsyncNHttpServiceHandler extends NHttpHandlerBase
105                                       implements NHttpServiceHandler {
106 
107     protected final HttpResponseFactory responseFactory;
108 
109     protected NHttpRequestHandlerResolver handlerResolver;
110     protected HttpExpectationVerifier expectationVerifier;
111 
112     public AsyncNHttpServiceHandler(
113             final HttpProcessor httpProcessor,
114             final HttpResponseFactory responseFactory,
115             final ConnectionReuseStrategy connStrategy,
116             final ByteBufferAllocator allocator,
117             final HttpParams params) {
118         super(httpProcessor, connStrategy, allocator, params);
119         Args.notNull(responseFactory, "Response factory");
120         this.responseFactory = responseFactory;
121     }
122 
123     public AsyncNHttpServiceHandler(
124             final HttpProcessor httpProcessor,
125             final HttpResponseFactory responseFactory,
126             final ConnectionReuseStrategy connStrategy,
127             final HttpParams params) {
128         this(httpProcessor, responseFactory, connStrategy, HeapByteBufferAllocator.INSTANCE, params);
129     }
130 
131     public void setExpectationVerifier(final HttpExpectationVerifier expectationVerifier) {
132         this.expectationVerifier = expectationVerifier;
133     }
134 
135     public void setHandlerResolver(final NHttpRequestHandlerResolver handlerResolver) {
136         this.handlerResolver = handlerResolver;
137     }
138 
139     public void connected(final NHttpServerConnection conn) {
140         final HttpContext context = conn.getContext();
141 
142         final ServerConnState connState = new ServerConnState();
143         context.setAttribute(CONN_STATE, connState);
144         context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
145 
146         if (this.eventListener != null) {
147             this.eventListener.connectionOpen(conn);
148         }
149     }
150 
151     public void requestReceived(final NHttpServerConnection conn) {
152         final HttpContext context = conn.getContext();
153 
154         final ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
155 
156         final HttpRequest request = conn.getHttpRequest();
157         request.setParams(new DefaultedHttpParams(request.getParams(), this.params));
158 
159         connState.setRequest(request);
160 
161         final NHttpRequestHandler requestHandler = getRequestHandler(request);
162         connState.setRequestHandler(requestHandler);
163 
164         ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
165         if (!ver.lessEquals(HttpVersion.HTTP_1_1)) {
166             // Downgrade protocol version if greater than HTTP/1.1
167             ver = HttpVersion.HTTP_1_1;
168         }
169 
170         HttpResponse response;
171 
172         try {
173 
174             if (request instanceof HttpEntityEnclosingRequest) {
175                 final HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request;
176                 if (entityRequest.expectContinue()) {
177                     response = this.responseFactory.newHttpResponse(
178                             ver, HttpStatus.SC_CONTINUE, context);
179                     response.setParams(
180                             new DefaultedHttpParams(response.getParams(), this.params));
181 
182                     if (this.expectationVerifier != null) {
183                         try {
184                             this.expectationVerifier.verify(request, response, context);
185                         } catch (final HttpException ex) {
186                             response = this.responseFactory.newHttpResponse(
187                                     HttpVersion.HTTP_1_0,
188                                     HttpStatus.SC_INTERNAL_SERVER_ERROR,
189                                     context);
190                             response.setParams(
191                                     new DefaultedHttpParams(response.getParams(), this.params));
192                             handleException(ex, response);
193                         }
194                     }
195 
196                     if (response.getStatusLine().getStatusCode() < 200) {
197                         // Send 1xx response indicating the server expections
198                         // have been met
199                         conn.submitResponse(response);
200                     } else {
201                         conn.resetInput();
202                         sendResponse(conn, request, response);
203                     }
204                 }
205                 // Request content is expected.
206                 ConsumingNHttpEntity consumingEntity = null;
207 
208                 // Lookup request handler for this request
209                 if (requestHandler != null) {
210                     consumingEntity = requestHandler.entityRequest(entityRequest, context);
211                 }
212                 if (consumingEntity == null) {
213                     consumingEntity = new NullNHttpEntity(entityRequest.getEntity());
214                 }
215                 entityRequest.setEntity(consumingEntity);
216                 connState.setConsumingEntity(consumingEntity);
217 
218             } else {
219                 // No request content is expected.
220                 // Process request right away
221                 conn.suspendInput();
222                 processRequest(conn, request);
223             }
224 
225         } catch (final IOException ex) {
226             shutdownConnection(conn, ex);
227             if (this.eventListener != null) {
228                 this.eventListener.fatalIOException(ex, conn);
229             }
230         } catch (final HttpException ex) {
231             closeConnection(conn, ex);
232             if (this.eventListener != null) {
233                 this.eventListener.fatalProtocolException(ex, conn);
234             }
235         }
236 
237     }
238 
239     public void closed(final NHttpServerConnection conn) {
240         final HttpContext context = conn.getContext();
241 
242         final ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
243         try {
244             connState.reset();
245         } catch (final IOException ex) {
246             if (this.eventListener != null) {
247                 this.eventListener.fatalIOException(ex, conn);
248             }
249         }
250         if (this.eventListener != null) {
251             this.eventListener.connectionClosed(conn);
252         }
253     }
254 
255     public void exception(final NHttpServerConnection conn, final HttpException httpex) {
256         if (conn.isResponseSubmitted()) {
257             // There is not much that we can do if a response head
258             // has already been submitted
259             closeConnection(conn, httpex);
260             if (eventListener != null) {
261                 eventListener.fatalProtocolException(httpex, conn);
262             }
263             return;
264         }
265 
266         final HttpContext context = conn.getContext();
267         try {
268             final HttpResponse response = this.responseFactory.newHttpResponse(
269                     HttpVersion.HTTP_1_0, HttpStatus.SC_INTERNAL_SERVER_ERROR, context);
270             response.setParams(
271                     new DefaultedHttpParams(response.getParams(), this.params));
272             handleException(httpex, response);
273             response.setEntity(null);
274             sendResponse(conn, null, response);
275 
276         } catch (final IOException ex) {
277             shutdownConnection(conn, ex);
278             if (this.eventListener != null) {
279                 this.eventListener.fatalIOException(ex, conn);
280             }
281         } catch (final HttpException ex) {
282             closeConnection(conn, ex);
283             if (this.eventListener != null) {
284                 this.eventListener.fatalProtocolException(ex, conn);
285             }
286         }
287     }
288 
289     public void exception(final NHttpServerConnection conn, final IOException ex) {
290         shutdownConnection(conn, ex);
291 
292         if (this.eventListener != null) {
293             this.eventListener.fatalIOException(ex, conn);
294         }
295     }
296 
297     public void timeout(final NHttpServerConnection conn) {
298         handleTimeout(conn);
299     }
300 
301     public void inputReady(final NHttpServerConnection conn, final ContentDecoder decoder) {
302         final HttpContext context = conn.getContext();
303         final ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
304 
305         final HttpRequest request = connState.getRequest();
306         final ConsumingNHttpEntity consumingEntity = connState.getConsumingEntity();
307 
308         try {
309 
310             consumingEntity.consumeContent(decoder, conn);
311             if (decoder.isCompleted()) {
312                 conn.suspendInput();
313                 processRequest(conn, request);
314             }
315 
316         } catch (final IOException ex) {
317             shutdownConnection(conn, ex);
318             if (this.eventListener != null) {
319                 this.eventListener.fatalIOException(ex, conn);
320             }
321         } catch (final HttpException ex) {
322             closeConnection(conn, ex);
323             if (this.eventListener != null) {
324                 this.eventListener.fatalProtocolException(ex, conn);
325             }
326         }
327     }
328 
329     public void responseReady(final NHttpServerConnection conn) {
330         final HttpContext context = conn.getContext();
331         final ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
332 
333         if (connState.isHandled()) {
334             return;
335         }
336 
337         final HttpRequest request = connState.getRequest();
338 
339         try {
340 
341             final IOException ioex = connState.getIOException();
342             if (ioex != null) {
343                 throw ioex;
344             }
345 
346             final HttpException httpex = connState.getHttpException();
347             if (httpex != null) {
348                 final HttpResponse response = this.responseFactory.newHttpResponse(HttpVersion.HTTP_1_0,
349                         HttpStatus.SC_INTERNAL_SERVER_ERROR, context);
350                 response.setParams(
351                         new DefaultedHttpParams(response.getParams(), this.params));
352                 handleException(httpex, response);
353                 connState.setResponse(response);
354             }
355 
356             final HttpResponse response = connState.getResponse();
357             if (response != null) {
358                 connState.setHandled(true);
359                 sendResponse(conn, request, response);
360             }
361 
362         } catch (final IOException ex) {
363             shutdownConnection(conn, ex);
364             if (this.eventListener != null) {
365                 this.eventListener.fatalIOException(ex, conn);
366             }
367         } catch (final HttpException ex) {
368             closeConnection(conn, ex);
369             if (this.eventListener != null) {
370                 this.eventListener.fatalProtocolException(ex, conn);
371             }
372         }
373     }
374 
375     public void outputReady(final NHttpServerConnection conn, final ContentEncoder encoder) {
376         final HttpContext context = conn.getContext();
377         final ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
378 
379         final HttpResponse response = conn.getHttpResponse();
380 
381         try {
382             final ProducingNHttpEntity entity = connState.getProducingEntity();
383             entity.produceContent(encoder, conn);
384 
385             if (encoder.isCompleted()) {
386                 connState.finishOutput();
387                 if (!this.connStrategy.keepAlive(response, context)) {
388                     conn.close();
389                 } else {
390                     // Ready to process new request
391                     connState.reset();
392                     conn.requestInput();
393                 }
394                 responseComplete(response, context);
395             }
396 
397         } catch (final IOException ex) {
398             shutdownConnection(conn, ex);
399             if (this.eventListener != null) {
400                 this.eventListener.fatalIOException(ex, conn);
401             }
402         }
403     }
404 
405     private void handleException(final HttpException ex, final HttpResponse response) {
406         int code = HttpStatus.SC_INTERNAL_SERVER_ERROR;
407         if (ex instanceof MethodNotSupportedException) {
408             code = HttpStatus.SC_NOT_IMPLEMENTED;
409         } else if (ex instanceof UnsupportedHttpVersionException) {
410             code = HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED;
411         } else if (ex instanceof ProtocolException) {
412             code = HttpStatus.SC_BAD_REQUEST;
413         }
414         response.setStatusCode(code);
415 
416         final byte[] msg = EncodingUtils.getAsciiBytes(ex.getMessage());
417         final NByteArrayEntity entity = new NByteArrayEntity(msg);
418         entity.setContentType("text/plain; charset=US-ASCII");
419         response.setEntity(entity);
420     }
421 
422     /**
423      * @throws HttpException - not thrown currently
424      */
425     private void processRequest(
426             final NHttpServerConnection conn,
427             final HttpRequest request) throws IOException, HttpException {
428 
429         final HttpContext context = conn.getContext();
430         final ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
431 
432         ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
433 
434         if (!ver.lessEquals(HttpVersion.HTTP_1_1)) {
435             // Downgrade protocol version if greater than HTTP/1.1
436             ver = HttpVersion.HTTP_1_1;
437         }
438 
439         final NHttpResponseTrigger trigger = new ResponseTriggerImpl(connState, conn);
440         try {
441             this.httpProcessor.process(request, context);
442 
443             final NHttpRequestHandler handler = connState.getRequestHandler();
444             if (handler != null) {
445                 final HttpResponse response = this.responseFactory.newHttpResponse(
446                         ver, HttpStatus.SC_OK, context);
447                 response.setParams(
448                         new DefaultedHttpParams(response.getParams(), this.params));
449 
450                 handler.handle(
451                         request,
452                         response,
453                         trigger,
454                         context);
455             } else {
456                 final HttpResponse response = this.responseFactory.newHttpResponse(ver,
457                         HttpStatus.SC_NOT_IMPLEMENTED, context);
458                 response.setParams(
459                         new DefaultedHttpParams(response.getParams(), this.params));
460                 trigger.submitResponse(response);
461             }
462 
463         } catch (final HttpException ex) {
464             trigger.handleException(ex);
465         }
466     }
467 
468     private void sendResponse(
469             final NHttpServerConnection conn,
470             final HttpRequest request,
471             final HttpResponse response) throws IOException, HttpException {
472         final HttpContext context = conn.getContext();
473         final ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
474 
475         // Now that a response is ready, we can cleanup the listener for the request.
476         connState.finishInput();
477 
478         // Some processers need the request that generated this response.
479         context.setAttribute(ExecutionContext.HTTP_REQUEST, request);
480         this.httpProcessor.process(response, context);
481         context.setAttribute(ExecutionContext.HTTP_REQUEST, null);
482 
483         if (response.getEntity() != null && !canResponseHaveBody(request, response)) {
484             response.setEntity(null);
485         }
486 
487         final HttpEntity entity = response.getEntity();
488         if (entity != null) {
489             if (entity instanceof ProducingNHttpEntity) {
490                 connState.setProducingEntity((ProducingNHttpEntity) entity);
491             } else {
492                 connState.setProducingEntity(new NHttpEntityWrapper(entity));
493             }
494         }
495 
496         conn.submitResponse(response);
497 
498         if (entity == null) {
499             if (!this.connStrategy.keepAlive(response, context)) {
500                 conn.close();
501             } else {
502                 // Ready to process new request
503                 connState.reset();
504                 conn.requestInput();
505             }
506             responseComplete(response, context);
507         }
508     }
509 
510     /**
511      * Signals that this response has been fully sent. This will be called after
512      * submitting the response to a connection, if there is no entity in the
513      * response. If there is an entity, it will be called after the entity has
514      * completed.
515      */
516     protected void responseComplete(final HttpResponse response, final HttpContext context) {
517     }
518 
519     private NHttpRequestHandler getRequestHandler(final HttpRequest request) {
520         NHttpRequestHandler handler = null;
521          if (this.handlerResolver != null) {
522              final String requestURI = request.getRequestLine().getUri();
523              handler = this.handlerResolver.lookup(requestURI);
524          }
525 
526          return handler;
527     }
528 
529     protected static class ServerConnState {
530 
531         private volatile NHttpRequestHandler requestHandler;
532         private volatile HttpRequest request;
533         private volatile ConsumingNHttpEntity consumingEntity;
534         private volatile HttpResponse response;
535         private volatile ProducingNHttpEntity producingEntity;
536         private volatile IOException ioex;
537         private volatile HttpException httpex;
538         private volatile boolean handled;
539 
540         public void finishInput() throws IOException {
541             if (this.consumingEntity != null) {
542                 this.consumingEntity.finish();
543                 this.consumingEntity = null;
544             }
545         }
546 
547         public void finishOutput() throws IOException {
548             if (this.producingEntity != null) {
549                 this.producingEntity.finish();
550                 this.producingEntity = null;
551             }
552         }
553 
554         public void reset() throws IOException {
555             finishInput();
556             this.request = null;
557             finishOutput();
558             this.handled = false;
559             this.response = null;
560             this.ioex = null;
561             this.httpex = null;
562             this.requestHandler = null;
563         }
564 
565         public NHttpRequestHandler getRequestHandler() {
566             return this.requestHandler;
567         }
568 
569         public void setRequestHandler(final NHttpRequestHandler requestHandler) {
570             this.requestHandler = requestHandler;
571         }
572 
573         public HttpRequest getRequest() {
574             return this.request;
575         }
576 
577         public void setRequest(final HttpRequest request) {
578             this.request = request;
579         }
580 
581         public ConsumingNHttpEntity getConsumingEntity() {
582             return this.consumingEntity;
583         }
584 
585         public void setConsumingEntity(final ConsumingNHttpEntity consumingEntity) {
586             this.consumingEntity = consumingEntity;
587         }
588 
589         public HttpResponse getResponse() {
590             return this.response;
591         }
592 
593         public void setResponse(final HttpResponse response) {
594             this.response = response;
595         }
596 
597         public ProducingNHttpEntity getProducingEntity() {
598             return this.producingEntity;
599         }
600 
601         public void setProducingEntity(final ProducingNHttpEntity producingEntity) {
602             this.producingEntity = producingEntity;
603         }
604 
605         public IOException getIOException() {
606             return this.ioex;
607         }
608 
609         public IOException getIOExepction() {
610             return this.ioex;
611         }
612 
613         public void setIOException(final IOException ex) {
614             this.ioex = ex;
615         }
616 
617         public void setIOExepction(final IOException ex) {
618             this.ioex = ex;
619         }
620 
621         public HttpException getHttpException() {
622             return this.httpex;
623         }
624 
625         public HttpException getHttpExepction() {
626             return this.httpex;
627         }
628 
629         public void setHttpException(final HttpException ex) {
630             this.httpex = ex;
631         }
632 
633         public void setHttpExepction(final HttpException ex) {
634             this.httpex = ex;
635         }
636 
637         public boolean isHandled() {
638             return this.handled;
639         }
640 
641         public void setHandled(final boolean handled) {
642             this.handled = handled;
643         }
644 
645     }
646 
647     private static class ResponseTriggerImpl implements NHttpResponseTrigger {
648 
649         private final ServerConnState connState;
650         private final IOControl iocontrol;
651 
652         private volatile boolean triggered;
653 
654         public ResponseTriggerImpl(final ServerConnState connState, final IOControl iocontrol) {
655             super();
656             this.connState = connState;
657             this.iocontrol = iocontrol;
658         }
659 
660         public void submitResponse(final HttpResponse response) {
661             Args.notNull(response, "Response");
662             Asserts.check(!this.triggered, "Response already triggered");
663             this.triggered = true;
664             this.connState.setResponse(response);
665             this.iocontrol.requestOutput();
666         }
667 
668         public void handleException(final HttpException ex) {
669             Asserts.check(!this.triggered, "Response already triggered");
670             this.triggered = true;
671             this.connState.setHttpException(ex);
672             this.iocontrol.requestOutput();
673         }
674 
675         public void handleException(final IOException ex) {
676             Asserts.check(!this.triggered, "Response already triggered");
677             this.triggered = true;
678             this.connState.setIOException(ex);
679             this.iocontrol.requestOutput();
680         }
681 
682     }
683 
684 }