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.HttpStatus;
39  import org.apache.http.annotation.ThreadingBehavior;
40  import org.apache.http.annotation.Contract;
41  import org.apache.http.nio.ContentDecoder;
42  import org.apache.http.nio.ContentEncoder;
43  import org.apache.http.nio.NHttpClientConnection;
44  import org.apache.http.nio.NHttpClientHandler;
45  import org.apache.http.nio.entity.ConsumingNHttpEntity;
46  import org.apache.http.nio.entity.NHttpEntityWrapper;
47  import org.apache.http.nio.entity.ProducingNHttpEntity;
48  import org.apache.http.nio.util.ByteBufferAllocator;
49  import org.apache.http.nio.util.HeapByteBufferAllocator;
50  import org.apache.http.params.CoreProtocolPNames;
51  import org.apache.http.params.DefaultedHttpParams;
52  import org.apache.http.params.HttpParams;
53  import org.apache.http.protocol.ExecutionContext;
54  import org.apache.http.protocol.HttpContext;
55  import org.apache.http.protocol.HttpProcessor;
56  import org.apache.http.util.Args;
57  
58  /**
59   * Fully asynchronous HTTP client side protocol handler that implements the
60   * essential requirements of the HTTP protocol for the server side message
61   * processing as described by RFC 2616. It is capable of executing HTTP requests
62   * with nearly constant memory footprint. Only HTTP message heads are stored in
63   * memory, while content of message bodies is streamed directly from the entity
64   * to the underlying channel (and vice versa) using {@link ConsumingNHttpEntity}
65   * and {@link ProducingNHttpEntity} interfaces.
66   *
67   * When using this implementation, it is important to ensure that entities
68   * supplied for writing implement {@link ProducingNHttpEntity}. Doing so will allow
69   * the entity to be written out asynchronously. If entities supplied for writing
70   * do not implement the {@link ProducingNHttpEntity} interface, a delegate is
71   * added that buffers the entire contents in memory. Additionally, the
72   * buffering might take place in the I/O dispatch thread, which could cause I/O
73   * to block temporarily. For best results, one must ensure that all entities
74   * set on {@link HttpRequest}s from {@link NHttpRequestExecutionHandler}
75   * implement {@link ProducingNHttpEntity}.
76   *
77   * If incoming responses enclose a content entity,
78   * {@link NHttpRequestExecutionHandler} are expected to return a
79   * {@link ConsumingNHttpEntity} for reading the content. After the entity is
80   * finished reading the data,
81   * {@link NHttpRequestExecutionHandler#handleResponse(HttpResponse, HttpContext)}
82   * method is called to process the response.
83   * <p>
84   * The following parameters can be used to customize the behavior of this
85   * class:
86   * <ul>
87   *  <li>{@link org.apache.http.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li>
88   * </ul>
89   *
90   * @since 4.0
91   *
92   * @deprecated (4.2) use {@link HttpAsyncRequestExecutor} and {@link HttpAsyncRequester}
93   */
94  @Deprecated
95  @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
96  public class AsyncNHttpClientHandler extends NHttpHandlerBase
97                                       implements NHttpClientHandler {
98  
99      protected NHttpRequestExecutionHandler execHandler;
100 
101     public AsyncNHttpClientHandler(
102             final HttpProcessor httpProcessor,
103             final NHttpRequestExecutionHandler execHandler,
104             final ConnectionReuseStrategy connStrategy,
105             final ByteBufferAllocator allocator,
106             final HttpParams params) {
107         super(httpProcessor, connStrategy, allocator, params);
108         this.execHandler = Args.notNull(execHandler, "HTTP request execution handler");
109     }
110 
111     public AsyncNHttpClientHandler(
112             final HttpProcessor httpProcessor,
113             final NHttpRequestExecutionHandler execHandler,
114             final ConnectionReuseStrategy connStrategy,
115             final HttpParams params) {
116         this(httpProcessor, execHandler, connStrategy, HeapByteBufferAllocator.INSTANCE, params);
117     }
118 
119     public void connected(final NHttpClientConnection conn, final Object attachment) {
120         final HttpContext context = conn.getContext();
121 
122         initialize(conn, attachment);
123 
124         final ClientConnState connState = new ClientConnState();
125         context.setAttribute(CONN_STATE, connState);
126 
127         if (this.eventListener != null) {
128             this.eventListener.connectionOpen(conn);
129         }
130 
131         requestReady(conn);
132     }
133 
134     public void closed(final NHttpClientConnection conn) {
135         final HttpContext context = conn.getContext();
136 
137         final ClientConnState connState = (ClientConnState) context.getAttribute(CONN_STATE);
138         try {
139             connState.reset();
140         } catch (final IOException ex) {
141             if (this.eventListener != null) {
142                 this.eventListener.fatalIOException(ex, conn);
143             }
144         }
145 
146         this.execHandler.finalizeContext(context);
147 
148         if (this.eventListener != null) {
149             this.eventListener.connectionClosed(conn);
150         }
151     }
152 
153     public void exception(final NHttpClientConnection conn, final HttpException ex) {
154         closeConnection(conn, ex);
155         if (this.eventListener != null) {
156             this.eventListener.fatalProtocolException(ex, conn);
157         }
158     }
159 
160     public void exception(final NHttpClientConnection conn, final IOException ex) {
161         shutdownConnection(conn, ex);
162         if (this.eventListener != null) {
163             this.eventListener.fatalIOException(ex, conn);
164         }
165     }
166 
167     public void requestReady(final NHttpClientConnection conn) {
168         final HttpContext context = conn.getContext();
169 
170         final ClientConnState connState = (ClientConnState) context.getAttribute(CONN_STATE);
171         if (connState.getOutputState() != ClientConnState.READY) {
172             return;
173         }
174 
175         try {
176 
177             final HttpRequest request = this.execHandler.submitRequest(context);
178             if (request == null) {
179                 return;
180             }
181 
182             request.setParams(
183                     new DefaultedHttpParams(request.getParams(), this.params));
184 
185             context.setAttribute(ExecutionContext.HTTP_REQUEST, request);
186             this.httpProcessor.process(request, context);
187 
188             HttpEntityEnclosingRequest entityReq = null;
189             HttpEntity entity = null;
190 
191             if (request instanceof HttpEntityEnclosingRequest) {
192                 entityReq = (HttpEntityEnclosingRequest) request;
193                 entity = entityReq.getEntity();
194             }
195 
196             if (entity instanceof ProducingNHttpEntity) {
197                 connState.setProducingEntity((ProducingNHttpEntity) entity);
198             } else if (entity != null) {
199                 connState.setProducingEntity(new NHttpEntityWrapper(entity));
200             }
201 
202             connState.setRequest(request);
203             conn.submitRequest(request);
204             connState.setOutputState(ClientConnState.REQUEST_SENT);
205 
206             if (entityReq != null && entityReq.expectContinue()) {
207                 int timeout = conn.getSocketTimeout();
208                 connState.setTimeout(timeout);
209                 timeout = this.params.getIntParameter(
210                         CoreProtocolPNames.WAIT_FOR_CONTINUE, 3000);
211                 conn.setSocketTimeout(timeout);
212                 connState.setOutputState(ClientConnState.EXPECT_CONTINUE);
213             } else if (connState.getProducingEntity() != null) {
214                 connState.setOutputState(ClientConnState.REQUEST_BODY_STREAM);
215             }
216 
217         } catch (final IOException ex) {
218             shutdownConnection(conn, ex);
219             if (this.eventListener != null) {
220                 this.eventListener.fatalIOException(ex, conn);
221             }
222         } catch (final HttpException ex) {
223             closeConnection(conn, ex);
224             if (this.eventListener != null) {
225                 this.eventListener.fatalProtocolException(ex, conn);
226             }
227         }
228     }
229 
230     public void inputReady(final NHttpClientConnection conn, final ContentDecoder decoder) {
231         final HttpContext context = conn.getContext();
232 
233         final ClientConnState connState = (ClientConnState) context.getAttribute(CONN_STATE);
234 
235         final ConsumingNHttpEntity consumingEntity = connState.getConsumingEntity();
236 
237         try {
238             consumingEntity.consumeContent(decoder, conn);
239             if (decoder.isCompleted()) {
240                 processResponse(conn, connState);
241             }
242 
243         } catch (final IOException ex) {
244             shutdownConnection(conn, ex);
245             if (this.eventListener != null) {
246                 this.eventListener.fatalIOException(ex, conn);
247             }
248         } catch (final HttpException ex) {
249             closeConnection(conn, ex);
250             if (this.eventListener != null) {
251                 this.eventListener.fatalProtocolException(ex, conn);
252             }
253         }
254     }
255 
256     public void outputReady(final NHttpClientConnection conn, final ContentEncoder encoder) {
257         final HttpContext context = conn.getContext();
258         final ClientConnState connState = (ClientConnState) context.getAttribute(CONN_STATE);
259 
260         try {
261             if (connState.getOutputState() == ClientConnState.EXPECT_CONTINUE) {
262                 conn.suspendOutput();
263                 return;
264             }
265 
266             final ProducingNHttpEntity entity = connState.getProducingEntity();
267 
268             entity.produceContent(encoder, conn);
269             if (encoder.isCompleted()) {
270                 connState.setOutputState(ClientConnState.REQUEST_BODY_DONE);
271             }
272         } catch (final IOException ex) {
273             shutdownConnection(conn, ex);
274             if (this.eventListener != null) {
275                 this.eventListener.fatalIOException(ex, conn);
276             }
277         }
278     }
279 
280     public void responseReceived(final NHttpClientConnection conn) {
281         final HttpContext context = conn.getContext();
282         final ClientConnState connState = (ClientConnState) context.getAttribute(CONN_STATE);
283 
284         final HttpResponse response = conn.getHttpResponse();
285         response.setParams(
286                 new DefaultedHttpParams(response.getParams(), this.params));
287 
288         final HttpRequest request = connState.getRequest();
289         try {
290 
291             final int statusCode = response.getStatusLine().getStatusCode();
292             if (statusCode < HttpStatus.SC_OK) {
293                 // 1xx intermediate response
294                 if (statusCode == HttpStatus.SC_CONTINUE
295                         && connState.getOutputState() == ClientConnState.EXPECT_CONTINUE) {
296                     continueRequest(conn, connState);
297                 }
298                 return;
299             } else {
300                 connState.setResponse(response);
301                 if (connState.getOutputState() == ClientConnState.EXPECT_CONTINUE) {
302                     cancelRequest(conn, connState);
303                 } else if (connState.getOutputState() == ClientConnState.REQUEST_BODY_STREAM) {
304                     // Early response
305                     cancelRequest(conn, connState);
306                     connState.invalidate();
307                     conn.suspendOutput();
308                 }
309             }
310 
311             context.setAttribute(ExecutionContext.HTTP_RESPONSE, response);
312 
313             if (!canResponseHaveBody(request, response)) {
314                 conn.resetInput();
315                 response.setEntity(null);
316                 this.httpProcessor.process(response, context);
317                 processResponse(conn, connState);
318             } else {
319                 final HttpEntity entity = response.getEntity();
320                 if (entity != null) {
321                     ConsumingNHttpEntity consumingEntity = this.execHandler.responseEntity(
322                             response, context);
323                     if (consumingEntity == null) {
324                         consumingEntity = new NullNHttpEntity(entity);
325                     }
326                     response.setEntity(consumingEntity);
327                     connState.setConsumingEntity(consumingEntity);
328                     this.httpProcessor.process(response, context);
329                 }
330             }
331 
332 
333         } catch (final IOException ex) {
334             shutdownConnection(conn, ex);
335             if (this.eventListener != null) {
336                 this.eventListener.fatalIOException(ex, conn);
337             }
338         } catch (final HttpException ex) {
339             closeConnection(conn, ex);
340             if (this.eventListener != null) {
341                 this.eventListener.fatalProtocolException(ex, conn);
342             }
343         }
344     }
345 
346     public void timeout(final NHttpClientConnection conn) {
347         final HttpContext context = conn.getContext();
348         final ClientConnState connState = (ClientConnState) context.getAttribute(CONN_STATE);
349 
350         try {
351 
352             if (connState.getOutputState() == ClientConnState.EXPECT_CONTINUE) {
353                 continueRequest(conn, connState);
354                 return;
355             }
356 
357         } catch (final IOException ex) {
358             shutdownConnection(conn, ex);
359             if (this.eventListener != null) {
360                 this.eventListener.fatalIOException(ex, conn);
361             }
362         }
363 
364         handleTimeout(conn);
365     }
366 
367     private void initialize(
368             final NHttpClientConnection conn,
369             final Object attachment) {
370         final HttpContext context = conn.getContext();
371 
372         context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
373         this.execHandler.initalizeContext(context, attachment);
374     }
375 
376     /**
377      * @throws IOException - not thrown currently
378      */
379     private void continueRequest(
380             final NHttpClientConnection conn,
381             final ClientConnState connState) throws IOException {
382 
383         final int timeout = connState.getTimeout();
384         conn.setSocketTimeout(timeout);
385 
386         conn.requestOutput();
387         connState.setOutputState(ClientConnState.REQUEST_BODY_STREAM);
388     }
389 
390     private void cancelRequest(
391             final NHttpClientConnection conn,
392             final ClientConnState connState) throws IOException {
393 
394         final int timeout = connState.getTimeout();
395         conn.setSocketTimeout(timeout);
396 
397         conn.resetOutput();
398         connState.resetOutput();
399     }
400 
401     /**
402      * @throws HttpException - not thrown currently
403      */
404     private void processResponse(
405             final NHttpClientConnection conn,
406             final ClientConnState connState) throws IOException, HttpException {
407 
408         if (!connState.isValid()) {
409             conn.close();
410         }
411 
412         final HttpContext context = conn.getContext();
413         final HttpResponse response = connState.getResponse();
414         this.execHandler.handleResponse(response, context);
415         if (!this.connStrategy.keepAlive(response, context)) {
416             conn.close();
417         }
418 
419         if (conn.isOpen()) {
420             // Ready for another request
421             connState.resetInput();
422             connState.resetOutput();
423             conn.requestOutput();
424         }
425     }
426 
427     protected static class ClientConnState {
428 
429         public static final int READY                      = 0;
430         public static final int REQUEST_SENT               = 1;
431         public static final int EXPECT_CONTINUE            = 2;
432         public static final int REQUEST_BODY_STREAM        = 4;
433         public static final int REQUEST_BODY_DONE          = 8;
434         public static final int RESPONSE_RECEIVED          = 16;
435         public static final int RESPONSE_BODY_STREAM       = 32;
436         public static final int RESPONSE_BODY_DONE         = 64;
437 
438         private int outputState;
439 
440         private HttpRequest request;
441         private HttpResponse response;
442         private ConsumingNHttpEntity consumingEntity;
443         private ProducingNHttpEntity producingEntity;
444         private boolean valid;
445         private int timeout;
446 
447         public ClientConnState() {
448             super();
449             this.valid = true;
450         }
451 
452         public void setConsumingEntity(final ConsumingNHttpEntity consumingEntity) {
453             this.consumingEntity = consumingEntity;
454         }
455 
456         public void setProducingEntity(final ProducingNHttpEntity producingEntity) {
457             this.producingEntity = producingEntity;
458         }
459 
460         public ProducingNHttpEntity getProducingEntity() {
461             return producingEntity;
462         }
463 
464         public ConsumingNHttpEntity getConsumingEntity() {
465             return consumingEntity;
466         }
467 
468         public int getOutputState() {
469             return this.outputState;
470         }
471 
472         public void setOutputState(final int outputState) {
473             this.outputState = outputState;
474         }
475 
476         public HttpRequest getRequest() {
477             return this.request;
478         }
479 
480         public void setRequest(final HttpRequest request) {
481             this.request = request;
482         }
483 
484         public HttpResponse getResponse() {
485             return this.response;
486         }
487 
488         public void setResponse(final HttpResponse response) {
489             this.response = response;
490         }
491 
492         public int getTimeout() {
493             return this.timeout;
494         }
495 
496         public void setTimeout(final int timeout) {
497             this.timeout = timeout;
498         }
499 
500         public void resetInput() throws IOException {
501             this.response = null;
502             if (this.consumingEntity != null) {
503                 this.consumingEntity.finish();
504                 this.consumingEntity = null;
505             }
506         }
507 
508         public void resetOutput() throws IOException {
509             this.request = null;
510             if (this.producingEntity != null) {
511                 this.producingEntity.finish();
512                 this.producingEntity = null;
513             }
514             this.outputState = READY;
515         }
516 
517         public void reset() throws IOException {
518             resetInput();
519             resetOutput();
520         }
521 
522         public boolean isValid() {
523             return this.valid;
524         }
525 
526         public void invalidate() {
527             this.valid = false;
528         }
529 
530     }
531 
532 }