001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.impl.execchain;
029
030import java.io.IOException;
031import java.io.InterruptedIOException;
032import java.net.URI;
033import java.net.URISyntaxException;
034import java.util.concurrent.ExecutionException;
035import java.util.concurrent.TimeUnit;
036
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039import org.apache.http.ConnectionReuseStrategy;
040import org.apache.http.HttpClientConnection;
041import org.apache.http.HttpEntity;
042import org.apache.http.HttpException;
043import org.apache.http.HttpHost;
044import org.apache.http.HttpRequest;
045import org.apache.http.HttpResponse;
046import org.apache.http.ProtocolException;
047import org.apache.http.annotation.Contract;
048import org.apache.http.annotation.ThreadingBehavior;
049import org.apache.http.client.config.RequestConfig;
050import org.apache.http.client.methods.CloseableHttpResponse;
051import org.apache.http.client.methods.HttpExecutionAware;
052import org.apache.http.client.methods.HttpRequestWrapper;
053import org.apache.http.client.methods.HttpUriRequest;
054import org.apache.http.client.protocol.HttpClientContext;
055import org.apache.http.client.protocol.RequestClientConnControl;
056import org.apache.http.client.utils.URIUtils;
057import org.apache.http.conn.ConnectionKeepAliveStrategy;
058import org.apache.http.conn.ConnectionRequest;
059import org.apache.http.conn.HttpClientConnectionManager;
060import org.apache.http.conn.routing.HttpRoute;
061import org.apache.http.impl.conn.ConnectionShutdownException;
062import org.apache.http.protocol.HttpCoreContext;
063import org.apache.http.protocol.HttpProcessor;
064import org.apache.http.protocol.HttpRequestExecutor;
065import org.apache.http.protocol.ImmutableHttpProcessor;
066import org.apache.http.protocol.RequestContent;
067import org.apache.http.protocol.RequestTargetHost;
068import org.apache.http.protocol.RequestUserAgent;
069import org.apache.http.util.Args;
070import org.apache.http.util.VersionInfo;
071
072/**
073 * Request executor that implements the most fundamental aspects of
074 * the HTTP specification and the most straight-forward request / response
075 * exchange with the target server. This executor does not support
076 * execution via proxy and will make no attempts to retry the request
077 * in case of a redirect, authentication challenge or I/O error.
078 *
079 * @since 4.3
080 */
081@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
082public class MinimalClientExec implements ClientExecChain {
083
084    private final Log log = LogFactory.getLog(getClass());
085
086    private final HttpRequestExecutor requestExecutor;
087    private final HttpClientConnectionManager connManager;
088    private final ConnectionReuseStrategy reuseStrategy;
089    private final ConnectionKeepAliveStrategy keepAliveStrategy;
090    private final HttpProcessor httpProcessor;
091
092    public MinimalClientExec(
093            final HttpRequestExecutor requestExecutor,
094            final HttpClientConnectionManager connManager,
095            final ConnectionReuseStrategy reuseStrategy,
096            final ConnectionKeepAliveStrategy keepAliveStrategy) {
097        Args.notNull(requestExecutor, "HTTP request executor");
098        Args.notNull(connManager, "Client connection manager");
099        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}