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.net.URI; 032import java.util.List; 033 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.apache.http.HttpEntityEnclosingRequest; 037import org.apache.http.HttpException; 038import org.apache.http.HttpHost; 039import org.apache.http.HttpRequest; 040import org.apache.http.ProtocolException; 041import org.apache.http.annotation.Contract; 042import org.apache.http.annotation.ThreadingBehavior; 043import org.apache.http.auth.AuthScheme; 044import org.apache.http.auth.AuthState; 045import org.apache.http.client.RedirectException; 046import org.apache.http.client.RedirectStrategy; 047import org.apache.http.client.config.RequestConfig; 048import org.apache.http.client.methods.CloseableHttpResponse; 049import org.apache.http.client.methods.HttpExecutionAware; 050import org.apache.http.client.methods.HttpRequestWrapper; 051import org.apache.http.client.protocol.HttpClientContext; 052import org.apache.http.client.utils.URIUtils; 053import org.apache.http.conn.routing.HttpRoute; 054import org.apache.http.conn.routing.HttpRoutePlanner; 055import org.apache.http.util.Args; 056import org.apache.http.util.EntityUtils; 057 058/** 059 * Request executor in the request execution chain that is responsible 060 * for handling of request redirects. 061 * <p> 062 * Further responsibilities such as communication with the opposite 063 * endpoint is delegated to the next executor in the request execution 064 * chain. 065 * </p> 066 * 067 * @since 4.3 068 */ 069@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) 070public class RedirectExec implements ClientExecChain { 071 072 private final Log log = LogFactory.getLog(getClass()); 073 074 private final ClientExecChain requestExecutor; 075 private final RedirectStrategy redirectStrategy; 076 private final HttpRoutePlanner routePlanner; 077 078 public RedirectExec( 079 final ClientExecChain requestExecutor, 080 final HttpRoutePlanner routePlanner, 081 final RedirectStrategy redirectStrategy) { 082 super(); 083 Args.notNull(requestExecutor, "HTTP client request executor"); 084 Args.notNull(routePlanner, "HTTP route planner"); 085 Args.notNull(redirectStrategy, "HTTP redirect strategy"); 086 this.requestExecutor = requestExecutor; 087 this.routePlanner = routePlanner; 088 this.redirectStrategy = redirectStrategy; 089 } 090 091 @Override 092 public CloseableHttpResponse execute( 093 final HttpRoute route, 094 final HttpRequestWrapper request, 095 final HttpClientContext context, 096 final HttpExecutionAware execAware) throws IOException, HttpException { 097 Args.notNull(route, "HTTP route"); 098 Args.notNull(request, "HTTP request"); 099 Args.notNull(context, "HTTP context"); 100 101 final List<URI> redirectLocations = context.getRedirectLocations(); 102 if (redirectLocations != null) { 103 redirectLocations.clear(); 104 } 105 106 final RequestConfig config = context.getRequestConfig(); 107 final int maxRedirects = config.getMaxRedirects() > 0 ? config.getMaxRedirects() : 50; 108 HttpRoute currentRoute = route; 109 HttpRequestWrapper currentRequest = request; 110 for (int redirectCount = 0;;) { 111 final CloseableHttpResponse response = requestExecutor.execute( 112 currentRoute, currentRequest, context, execAware); 113 try { 114 if (config.isRedirectsEnabled() && 115 this.redirectStrategy.isRedirected(currentRequest.getOriginal(), response, context)) { 116 117 if (redirectCount >= maxRedirects) { 118 throw new RedirectException("Maximum redirects ("+ maxRedirects + ") exceeded"); 119 } 120 redirectCount++; 121 122 final HttpRequest redirect = this.redirectStrategy.getRedirect( 123 currentRequest.getOriginal(), response, context); 124 if (!redirect.headerIterator().hasNext()) { 125 final HttpRequest original = request.getOriginal(); 126 redirect.setHeaders(original.getAllHeaders()); 127 } 128 currentRequest = HttpRequestWrapper.wrap(redirect); 129 130 if (currentRequest instanceof HttpEntityEnclosingRequest) { 131 RequestEntityProxy.enhance((HttpEntityEnclosingRequest) currentRequest); 132 } 133 134 final URI uri = currentRequest.getURI(); 135 final HttpHost newTarget = URIUtils.extractHost(uri); 136 if (newTarget == null) { 137 throw new ProtocolException("Redirect URI does not specify a valid host name: " + 138 uri); 139 } 140 141 // Reset virtual host and auth states if redirecting to another host 142 if (!currentRoute.getTargetHost().equals(newTarget)) { 143 final AuthState targetAuthState = context.getTargetAuthState(); 144 if (targetAuthState != null) { 145 this.log.debug("Resetting target auth state"); 146 targetAuthState.reset(); 147 } 148 final AuthState proxyAuthState = context.getProxyAuthState(); 149 if (proxyAuthState != null) { 150 final AuthScheme authScheme = proxyAuthState.getAuthScheme(); 151 if (authScheme != null && authScheme.isConnectionBased()) { 152 this.log.debug("Resetting proxy auth state"); 153 proxyAuthState.reset(); 154 } 155 } 156 } 157 158 currentRoute = this.routePlanner.determineRoute(newTarget, currentRequest, context); 159 if (this.log.isDebugEnabled()) { 160 this.log.debug("Redirecting to '" + uri + "' via " + currentRoute); 161 } 162 EntityUtils.consume(response.getEntity()); 163 response.close(); 164 } else { 165 return response; 166 } 167 } catch (final RuntimeException ex) { 168 response.close(); 169 throw ex; 170 } catch (final IOException ex) { 171 response.close(); 172 throw ex; 173 } catch (final HttpException ex) { 174 // Protocol exception related to a direct. 175 // The underlying connection may still be salvaged. 176 try { 177 EntityUtils.consume(response.getEntity()); 178 } catch (final IOException ioex) { 179 this.log.debug("I/O error while releasing connection", ioex); 180 } finally { 181 response.close(); 182 } 183 throw ex; 184 } 185 } 186 } 187 188}