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