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