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 ch.boye.httpclientandroidlib.impl.execchain; 29 30 import java.io.IOException; 31 import java.net.URI; 32 import java.util.List; 33 34 import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; 35 /* LogFactory removed by HttpClient for Android script. */ 36 import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; 37 import ch.boye.httpclientandroidlib.HttpException; 38 import ch.boye.httpclientandroidlib.HttpHost; 39 import ch.boye.httpclientandroidlib.HttpRequest; 40 import ch.boye.httpclientandroidlib.ProtocolException; 41 import ch.boye.httpclientandroidlib.annotation.ThreadSafe; 42 import ch.boye.httpclientandroidlib.auth.AuthScheme; 43 import ch.boye.httpclientandroidlib.auth.AuthState; 44 import ch.boye.httpclientandroidlib.client.RedirectException; 45 import ch.boye.httpclientandroidlib.client.RedirectStrategy; 46 import ch.boye.httpclientandroidlib.client.config.RequestConfig; 47 import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; 48 import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; 49 import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; 50 import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; 51 import ch.boye.httpclientandroidlib.client.utils.URIUtils; 52 import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; 53 import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; 54 import ch.boye.httpclientandroidlib.util.Args; 55 import ch.boye.httpclientandroidlib.util.EntityUtils; 56 57 /** 58 * Request executor in the request execution chain that is responsible 59 * for handling of request redirects. 60 * <p/> 61 * Further responsibilities such as communication with the opposite 62 * endpoint is delegated to the next executor in the request execution 63 * chain. 64 * 65 * @since 4.3 66 */ 67 @ThreadSafe 68 public class RedirectExec implements ClientExecChain { 69 70 public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); 71 72 private final ClientExecChain requestExecutor; 73 private final RedirectStrategy redirectStrategy; 74 private final HttpRoutePlanner routePlanner; 75 RedirectExec( final ClientExecChain requestExecutor, final HttpRoutePlanner routePlanner, final RedirectStrategy redirectStrategy)76 public RedirectExec( 77 final ClientExecChain requestExecutor, 78 final HttpRoutePlanner routePlanner, 79 final RedirectStrategy redirectStrategy) { 80 super(); 81 Args.notNull(requestExecutor, "HTTP client request executor"); 82 Args.notNull(routePlanner, "HTTP route planner"); 83 Args.notNull(redirectStrategy, "HTTP redirect strategy"); 84 this.requestExecutor = requestExecutor; 85 this.routePlanner = routePlanner; 86 this.redirectStrategy = redirectStrategy; 87 } 88 execute( final HttpRoute route, final HttpRequestWrapper request, final HttpClientContext context, final HttpExecutionAware execAware)89 public CloseableHttpResponse execute( 90 final HttpRoute route, 91 final HttpRequestWrapper request, 92 final HttpClientContext context, 93 final HttpExecutionAware execAware) throws IOException, HttpException { 94 Args.notNull(route, "HTTP route"); 95 Args.notNull(request, "HTTP request"); 96 Args.notNull(context, "HTTP context"); 97 98 final List<URI> redirectLocations = context.getRedirectLocations(); 99 if (redirectLocations != null) { 100 redirectLocations.clear(); 101 } 102 103 final RequestConfig config = context.getRequestConfig(); 104 final int maxRedirects = config.getMaxRedirects() > 0 ? config.getMaxRedirects() : 50; 105 HttpRoute currentRoute = route; 106 HttpRequestWrapper currentRequest = request; 107 for (int redirectCount = 0;;) { 108 final CloseableHttpResponse response = requestExecutor.execute( 109 currentRoute, currentRequest, context, execAware); 110 try { 111 if (config.isRedirectsEnabled() && 112 this.redirectStrategy.isRedirected(currentRequest, response, context)) { 113 114 if (redirectCount >= maxRedirects) { 115 throw new RedirectException("Maximum redirects ("+ maxRedirects + ") exceeded"); 116 } 117 redirectCount++; 118 119 final HttpRequest redirect = this.redirectStrategy.getRedirect( 120 currentRequest, response, context); 121 if (!redirect.headerIterator().hasNext()) { 122 final HttpRequest original = request.getOriginal(); 123 redirect.setHeaders(original.getAllHeaders()); 124 } 125 currentRequest = HttpRequestWrapper.wrap(redirect); 126 127 if (currentRequest instanceof HttpEntityEnclosingRequest) { 128 RequestEntityProxy.enhance((HttpEntityEnclosingRequest) currentRequest); 129 } 130 131 final URI uri = currentRequest.getURI(); 132 final HttpHost newTarget = URIUtils.extractHost(uri); 133 if (newTarget == null) { 134 throw new ProtocolException("Redirect URI does not specify a valid host name: " + 135 uri); 136 } 137 138 // Reset virtual host and auth states if redirecting to another host 139 if (!currentRoute.getTargetHost().equals(newTarget)) { 140 final AuthState targetAuthState = context.getTargetAuthState(); 141 if (targetAuthState != null) { 142 this.log.debug("Resetting target auth state"); 143 targetAuthState.reset(); 144 } 145 final AuthState proxyAuthState = context.getProxyAuthState(); 146 if (proxyAuthState != null) { 147 final AuthScheme authScheme = proxyAuthState.getAuthScheme(); 148 if (authScheme != null && authScheme.isConnectionBased()) { 149 this.log.debug("Resetting proxy auth state"); 150 proxyAuthState.reset(); 151 } 152 } 153 } 154 155 currentRoute = this.routePlanner.determineRoute(newTarget, currentRequest, context); 156 if (this.log.isDebugEnabled()) { 157 this.log.debug("Redirecting to '" + uri + "' via " + currentRoute); 158 } 159 EntityUtils.consume(response.getEntity()); 160 response.close(); 161 } else { 162 return response; 163 } 164 } catch (final RuntimeException ex) { 165 response.close(); 166 throw ex; 167 } catch (final IOException ex) { 168 response.close(); 169 throw ex; 170 } catch (final HttpException ex) { 171 // Protocol exception related to a direct. 172 // The underlying connection may still be salvaged. 173 try { 174 EntityUtils.consume(response.getEntity()); 175 } catch (final IOException ioex) { 176 this.log.debug("I/O error while releasing connection", ioex); 177 } finally { 178 response.close(); 179 } 180 throw ex; 181 } 182 } 183 } 184 185 } 186