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}