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