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