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.impl.execchain;
29  
30  import java.io.IOException;
31  import java.io.InterruptedIOException;
32  import java.net.URI;
33  import java.net.URISyntaxException;
34  import java.util.concurrent.ExecutionException;
35  import java.util.concurrent.TimeUnit;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.apache.http.ConnectionReuseStrategy;
40  import org.apache.http.HttpClientConnection;
41  import org.apache.http.HttpEntity;
42  import org.apache.http.HttpException;
43  import org.apache.http.HttpHost;
44  import org.apache.http.HttpRequest;
45  import org.apache.http.HttpResponse;
46  import org.apache.http.ProtocolException;
47  import org.apache.http.annotation.Immutable;
48  import org.apache.http.client.config.RequestConfig;
49  import org.apache.http.client.methods.CloseableHttpResponse;
50  import org.apache.http.client.methods.HttpExecutionAware;
51  import org.apache.http.client.methods.HttpRequestWrapper;
52  import org.apache.http.client.methods.HttpUriRequest;
53  import org.apache.http.client.protocol.HttpClientContext;
54  import org.apache.http.client.protocol.RequestClientConnControl;
55  import org.apache.http.client.utils.URIUtils;
56  import org.apache.http.conn.ConnectionKeepAliveStrategy;
57  import org.apache.http.conn.ConnectionRequest;
58  import org.apache.http.conn.HttpClientConnectionManager;
59  import org.apache.http.conn.routing.HttpRoute;
60  import org.apache.http.impl.conn.ConnectionShutdownException;
61  import org.apache.http.protocol.HttpCoreContext;
62  import org.apache.http.protocol.HttpProcessor;
63  import org.apache.http.protocol.HttpRequestExecutor;
64  import org.apache.http.protocol.ImmutableHttpProcessor;
65  import org.apache.http.protocol.RequestContent;
66  import org.apache.http.protocol.RequestTargetHost;
67  import org.apache.http.protocol.RequestUserAgent;
68  import org.apache.http.util.Args;
69  import org.apache.http.util.VersionInfo;
70  
71  /**
72   * Request executor that implements the most fundamental aspects of
73   * the HTTP specification and the most straight-forward request / response
74   * exchange with the target server. This executor does not support
75   * execution via proxy and will make no attempts to retry the request
76   * in case of a redirect, authentication challenge or I/O error.
77   *
78   * @since 4.3
79   */
80  @Immutable
81  public class MinimalClientExec implements ClientExecChain {
82  
83      private final Log log = LogFactory.getLog(getClass());
84  
85      private final HttpRequestExecutor requestExecutor;
86      private final HttpClientConnectionManager connManager;
87      private final ConnectionReuseStrategy reuseStrategy;
88      private final ConnectionKeepAliveStrategy keepAliveStrategy;
89      private final HttpProcessor httpProcessor;
90  
91      public MinimalClientExec(
92              final HttpRequestExecutor requestExecutor,
93              final HttpClientConnectionManager connManager,
94              final ConnectionReuseStrategy reuseStrategy,
95              final ConnectionKeepAliveStrategy keepAliveStrategy) {
96          Args.notNull(requestExecutor, "HTTP request executor");
97          Args.notNull(connManager, "Client connection manager");
98          Args.notNull(reuseStrategy, "Connection reuse strategy");
99          Args.notNull(keepAliveStrategy, "Connection keep alive strategy");
100         this.httpProcessor = new ImmutableHttpProcessor(
101                 new RequestContent(),
102                 new RequestTargetHost(),
103                 new RequestClientConnControl(),
104                 new RequestUserAgent(VersionInfo.getUserAgent(
105                         "Apache-HttpClient", "org.apache.http.client", getClass())));
106         this.requestExecutor    = requestExecutor;
107         this.connManager        = connManager;
108         this.reuseStrategy      = reuseStrategy;
109         this.keepAliveStrategy  = keepAliveStrategy;
110     }
111 
112     static void rewriteRequestURI(
113             final HttpRequestWrapper request,
114             final HttpRoute route) throws ProtocolException {
115         try {
116             URI uri = request.getURI();
117             if (uri != null) {
118                 // Make sure the request URI is relative
119                 if (uri.isAbsolute()) {
120                     uri = URIUtils.rewriteURI(uri, null, true);
121                 } else {
122                     uri = URIUtils.rewriteURI(uri);
123                 }
124                 request.setURI(uri);
125             }
126         } catch (final URISyntaxException ex) {
127             throw new ProtocolException("Invalid URI: " + request.getRequestLine().getUri(), ex);
128         }
129     }
130 
131     public CloseableHttpResponse execute(
132             final HttpRoute route,
133             final HttpRequestWrapper request,
134             final HttpClientContext context,
135             final HttpExecutionAware execAware) throws IOException, HttpException {
136         Args.notNull(route, "HTTP route");
137         Args.notNull(request, "HTTP request");
138         Args.notNull(context, "HTTP context");
139 
140         rewriteRequestURI(request, route);
141 
142         final ConnectionRequest connRequest = connManager.requestConnection(route, null);
143         if (execAware != null) {
144             if (execAware.isAborted()) {
145                 connRequest.cancel();
146                 throw new RequestAbortedException("Request aborted");
147             } else {
148                 execAware.setCancellable(connRequest);
149             }
150         }
151 
152         final RequestConfig config = context.getRequestConfig();
153 
154         final HttpClientConnection managedConn;
155         try {
156             final int timeout = config.getConnectionRequestTimeout();
157             managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS);
158         } catch(final InterruptedException interrupted) {
159             Thread.currentThread().interrupt();
160             throw new RequestAbortedException("Request aborted", interrupted);
161         } catch(final ExecutionException ex) {
162             Throwable cause = ex.getCause();
163             if (cause == null) {
164                 cause = ex;
165             }
166             throw new RequestAbortedException("Request execution failed", cause);
167         }
168 
169         final ConnectionHolder releaseTrigger = new ConnectionHolder(log, connManager, managedConn);
170         try {
171             if (execAware != null) {
172                 if (execAware.isAborted()) {
173                     releaseTrigger.close();
174                     throw new RequestAbortedException("Request aborted");
175                 } else {
176                     execAware.setCancellable(releaseTrigger);
177                 }
178             }
179 
180             if (!managedConn.isOpen()) {
181                 final int timeout = config.getConnectTimeout();
182                 this.connManager.connect(
183                     managedConn,
184                     route,
185                     timeout > 0 ? timeout : 0,
186                     context);
187                 this.connManager.routeComplete(managedConn, route, context);
188             }
189             final int timeout = config.getSocketTimeout();
190             if (timeout >= 0) {
191                 managedConn.setSocketTimeout(timeout);
192             }
193 
194             HttpHost target = null;
195             final HttpRequest original = request.getOriginal();
196             if (original instanceof HttpUriRequest) {
197                 final URI uri = ((HttpUriRequest) original).getURI();
198                 if (uri.isAbsolute()) {
199                     target = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
200                 }
201             }
202             if (target == null) {
203                 target = route.getTargetHost();
204             }
205 
206             context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target);
207             context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
208             context.setAttribute(HttpCoreContext.HTTP_CONNECTION, managedConn);
209             context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
210 
211             httpProcessor.process(request, context);
212             final HttpResponse response = requestExecutor.execute(request, managedConn, context);
213             httpProcessor.process(response, context);
214 
215             // The connection is in or can be brought to a re-usable state.
216             if (reuseStrategy.keepAlive(response, context)) {
217                 // Set the idle duration of this connection
218                 final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
219                 releaseTrigger.setValidFor(duration, TimeUnit.MILLISECONDS);
220                 releaseTrigger.markReusable();
221             } else {
222                 releaseTrigger.markNonReusable();
223             }
224 
225             // check for entity, release connection if possible
226             final HttpEntity entity = response.getEntity();
227             if (entity == null || !entity.isStreaming()) {
228                 // connection not needed and (assumed to be) in re-usable state
229                 releaseTrigger.releaseConnection();
230                 return new HttpResponseProxy(response, null);
231             } else {
232                 return new HttpResponseProxy(response, releaseTrigger);
233             }
234         } catch (final ConnectionShutdownException ex) {
235             final InterruptedIOException ioex = new InterruptedIOException(
236                     "Connection has been shut down");
237             ioex.initCause(ex);
238             throw ioex;
239         } catch (final HttpException ex) {
240             releaseTrigger.abortConnection();
241             throw ex;
242         } catch (final IOException ex) {
243             releaseTrigger.abortConnection();
244             throw ex;
245         } catch (final RuntimeException ex) {
246             releaseTrigger.abortConnection();
247             throw ex;
248         }
249     }
250 
251 }