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