View Javadoc
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 org.apache.http.impl.client;
29  
30  import java.net.URI;
31  import java.net.URISyntaxException;
32  import java.util.Arrays;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.http.Header;
37  import org.apache.http.HttpHost;
38  import org.apache.http.HttpRequest;
39  import org.apache.http.HttpResponse;
40  import org.apache.http.HttpStatus;
41  import org.apache.http.ProtocolException;
42  import org.apache.http.annotation.Contract;
43  import org.apache.http.annotation.ThreadingBehavior;
44  import org.apache.http.client.CircularRedirectException;
45  import org.apache.http.client.RedirectStrategy;
46  import org.apache.http.client.config.RequestConfig;
47  import org.apache.http.client.methods.HttpGet;
48  import org.apache.http.client.methods.HttpHead;
49  import org.apache.http.client.methods.HttpUriRequest;
50  import org.apache.http.client.methods.RequestBuilder;
51  import org.apache.http.client.protocol.HttpClientContext;
52  import org.apache.http.client.utils.URIUtils;
53  import org.apache.http.protocol.HttpContext;
54  import org.apache.http.util.Args;
55  import org.apache.http.util.Asserts;
56  
57  /**
58   * Default implementation of {@link RedirectStrategy}. This strategy honors the restrictions
59   * on automatic redirection of entity enclosing methods such as POST and PUT imposed by the
60   * HTTP specification. {@code 302 Moved Temporarily}, {@code 301 Moved Permanently} and
61   * {@code 307 Temporary Redirect} status codes will result in an automatic redirect of
62   * HEAD and GET methods only. POST and PUT methods will not be automatically redirected
63   * as requiring user confirmation.
64   * <p>
65   * The restriction on automatic redirection of POST methods can be relaxed by using
66   * {@link LaxRedirectStrategy} instead of {@link DefaultRedirectStrategy}.
67   * </p>
68   *
69   * @see LaxRedirectStrategy
70   * @since 4.1
71   */
72  @Contract(threading = ThreadingBehavior.IMMUTABLE)
73  public class DefaultRedirectStrategy implements RedirectStrategy {
74  
75      private final Log log = LogFactory.getLog(getClass());
76  
77      public static final int SC_PERMANENT_REDIRECT = 308;
78  
79      /**
80       * @deprecated (4.3) use {@link org.apache.http.client.protocol.HttpClientContext#REDIRECT_LOCATIONS}.
81       */
82      @Deprecated
83      public static final String REDIRECT_LOCATIONS = "http.protocol.redirect-locations";
84  
85      public static final DefaultRedirectStrategytrategy.html#DefaultRedirectStrategy">DefaultRedirectStrategy INSTANCE = new DefaultRedirectStrategy();
86  
87      private final String[] redirectMethods;
88  
89      public DefaultRedirectStrategy() {
90          this(new String[] {
91              HttpGet.METHOD_NAME,
92              HttpHead.METHOD_NAME
93          });
94      }
95  
96      /**
97       * Constructs a new instance to redirect the given HTTP methods.
98       *
99       * @param redirectMethods The methods to redirect.
100      * @since 4.5.10
101      */
102     public DefaultRedirectStrategy(final String[] redirectMethods) {
103         super();
104         final String[] tmp = redirectMethods.clone();
105         Arrays.sort(tmp);
106         this.redirectMethods = tmp;
107     }
108 
109     @Override
110     public boolean isRedirected(
111             final HttpRequest request,
112             final HttpResponse response,
113             final HttpContext context) throws ProtocolException {
114         Args.notNull(request, "HTTP request");
115         Args.notNull(response, "HTTP response");
116 
117         final int statusCode = response.getStatusLine().getStatusCode();
118         final String method = request.getRequestLine().getMethod();
119         final Header locationHeader = response.getFirstHeader("location");
120         switch (statusCode) {
121         case HttpStatus.SC_MOVED_TEMPORARILY:
122             return isRedirectable(method) && locationHeader != null;
123         case HttpStatus.SC_MOVED_PERMANENTLY:
124         case HttpStatus.SC_TEMPORARY_REDIRECT:
125         case SC_PERMANENT_REDIRECT:
126             return isRedirectable(method);
127         case HttpStatus.SC_SEE_OTHER:
128             return true;
129         default:
130             return false;
131         } //end of switch
132     }
133 
134     public URI getLocationURI(
135             final HttpRequest request,
136             final HttpResponse response,
137             final HttpContext context) throws ProtocolException {
138         Args.notNull(request, "HTTP request");
139         Args.notNull(response, "HTTP response");
140         Args.notNull(context, "HTTP context");
141 
142         final HttpClientContext clientContext = HttpClientContext.adapt(context);
143 
144         //get the location header to find out where to redirect to
145         final Header locationHeader = response.getFirstHeader("location");
146         if (locationHeader == null) {
147             // got a redirect response, but no location header
148             throw new ProtocolException(
149                     "Received redirect response " + response.getStatusLine()
150                     + " but no location header");
151         }
152         final String location = locationHeader.getValue();
153         if (this.log.isDebugEnabled()) {
154             this.log.debug("Redirect requested to location '" + location + "'");
155         }
156 
157         final RequestConfig config = clientContext.getRequestConfig();
158 
159         URI uri = createLocationURI(location);
160 
161         try {
162             if (config.isNormalizeUri()) {
163                 uri = URIUtils.normalizeSyntax(uri);
164             }
165 
166             // rfc2616 demands the location value be a complete URI
167             // Location       = "Location" ":" absoluteURI
168             if (!uri.isAbsolute()) {
169                 if (!config.isRelativeRedirectsAllowed()) {
170                     throw new ProtocolException("Relative redirect location '"
171                             + uri + "' not allowed");
172                 }
173                 // Adjust location URI
174                 final HttpHost target = clientContext.getTargetHost();
175                 Asserts.notNull(target, "Target host");
176                 final URI requestURI = new URI(request.getRequestLine().getUri());
177                 final URI absoluteRequestURI = URIUtils.rewriteURI(requestURI, target,
178                     config.isNormalizeUri() ? URIUtils.NORMALIZE : URIUtils.NO_FLAGS);
179                 uri = URIUtils.resolve(absoluteRequestURI, uri);
180             }
181         } catch (final URISyntaxException ex) {
182             throw new ProtocolException(ex.getMessage(), ex);
183         }
184 
185         RedirectLocationsche/http/impl/client/RedirectLocations.html#RedirectLocations">RedirectLocations redirectLocations = (RedirectLocations) clientContext.getAttribute(
186                 HttpClientContext.REDIRECT_LOCATIONS);
187         if (redirectLocations == null) {
188             redirectLocations = new RedirectLocations();
189             context.setAttribute(HttpClientContext.REDIRECT_LOCATIONS, redirectLocations);
190         }
191         if (!config.isCircularRedirectsAllowed()) {
192             if (redirectLocations.contains(uri)) {
193                 throw new CircularRedirectException("Circular redirect to '" + uri + "'");
194             }
195         }
196         redirectLocations.add(uri);
197         return uri;
198     }
199 
200     /**
201      * @since 4.1
202      */
203     protected URI createLocationURI(final String location) throws ProtocolException {
204         try {
205             return new URI(location);
206         } catch (final URISyntaxException ex) {
207             throw new ProtocolException("Invalid redirect URI: " + location, ex);
208         }
209     }
210 
211     /**
212      * @since 4.2
213      */
214     protected boolean isRedirectable(final String method) {
215         return Arrays.binarySearch(redirectMethods, method) >= 0;
216     }
217 
218     @Override
219     public HttpUriRequest getRedirect(
220             final HttpRequest request,
221             final HttpResponse response,
222             final HttpContext context) throws ProtocolException {
223         final URI uri = getLocationURI(request, response, context);
224         final String method = request.getRequestLine().getMethod();
225         if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) {
226             return new HttpHead(uri);
227         } else if (method.equalsIgnoreCase(HttpGet.METHOD_NAME)) {
228             return new HttpGet(uri);
229         } else {
230             final int status = response.getStatusLine().getStatusCode();
231             return (status == HttpStatus.SC_TEMPORARY_REDIRECT || status == SC_PERMANENT_REDIRECT)
232                             ? RequestBuilder.copy(request).setUri(uri).build()
233                             : new HttpGet(uri);
234         }
235     }
236 
237 }