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