美文网首页
聊聊HttpClient的RedirectStrategy

聊聊HttpClient的RedirectStrategy

作者: go4it | 来源:发表于2023-10-17 09:15 被阅读0次

    本文主要研究一下HttpClient的RedirectStrategy

    RedirectStrategy

    org/apache/http/client/RedirectStrategy.java

    public interface RedirectStrategy {
    
        /**
         * Determines if a request should be redirected to a new location
         * given the response from the target server.
         *
         * @param request the executed request
         * @param response the response received from the target server
         * @param context the context for the request execution
         *
         * @return {@code true} if the request should be redirected, {@code false}
         * otherwise
         */
        boolean isRedirected(
                HttpRequest request,
                HttpResponse response,
                HttpContext context) throws ProtocolException;
    
        /**
         * Determines the redirect location given the response from the target
         * server and the current request execution context and generates a new
         * request to be sent to the location.
         *
         * @param request the executed request
         * @param response the response received from the target server
         * @param context the context for the request execution
         *
         * @return redirected request
         */
        HttpUriRequest getRedirect(
                HttpRequest request,
                HttpResponse response,
                HttpContext context) throws ProtocolException;
    
    }
    

    RedirectStrategy接口定义了isRedirected方法用于判断是否需要redirect,还定义了getRedirect方法用于返回redirect的目标地址

    DefaultRedirectStrategy

    org/apache/http/impl/client/DefaultRedirectStrategy.java

    @Contract(threading = ThreadingBehavior.IMMUTABLE)
    public class DefaultRedirectStrategy implements RedirectStrategy {
    
        private final Log log = LogFactory.getLog(getClass());
    
        /**
         * @deprecated (4.3) use {@link org.apache.http.client.protocol.HttpClientContext#REDIRECT_LOCATIONS}.
         */
        @Deprecated
        public static final String REDIRECT_LOCATIONS = "http.protocol.redirect-locations";
    
        public static final DefaultRedirectStrategy INSTANCE = new DefaultRedirectStrategy();
    
        private final String[] redirectMethods;
    
        public DefaultRedirectStrategy() {
            this(new String[] {
                HttpGet.METHOD_NAME,
                HttpHead.METHOD_NAME
            });
        }
    
        /**
         * Constructs a new instance to redirect the given HTTP methods.
         *
         * @param redirectMethods The methods to redirect.
         * @since 4.5.10
         */
        public DefaultRedirectStrategy(final String[] redirectMethods) {
            super();
            final String[] tmp = redirectMethods.clone();
            Arrays.sort(tmp);
            this.redirectMethods = tmp;
        }
    
        @Override
        public boolean isRedirected(
                final HttpRequest request,
                final HttpResponse response,
                final HttpContext context) throws ProtocolException {
            Args.notNull(request, "HTTP request");
            Args.notNull(response, "HTTP response");
    
            final int statusCode = response.getStatusLine().getStatusCode();
            final String method = request.getRequestLine().getMethod();
            final Header locationHeader = response.getFirstHeader("location");
            switch (statusCode) {
            case HttpStatus.SC_MOVED_TEMPORARILY:
                return isRedirectable(method) && locationHeader != null;
            case HttpStatus.SC_MOVED_PERMANENTLY:
            case HttpStatus.SC_TEMPORARY_REDIRECT:
                return isRedirectable(method);
            case HttpStatus.SC_SEE_OTHER:
                return true;
            default:
                return false;
            } //end of switch
        }
    
        /**
         * @since 4.2
         */
        protected boolean isRedirectable(final String method) {
            return Arrays.binarySearch(redirectMethods, method) >= 0;
        }
    
        @Override
        public HttpUriRequest getRedirect(
                final HttpRequest request,
                final HttpResponse response,
                final HttpContext context) throws ProtocolException {
            final URI uri = getLocationURI(request, response, context);
            final String method = request.getRequestLine().getMethod();
            if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) {
                return new HttpHead(uri);
            } else if (method.equalsIgnoreCase(HttpGet.METHOD_NAME)) {
                return new HttpGet(uri);
            } else {
                final int status = response.getStatusLine().getStatusCode();
                return status == HttpStatus.SC_TEMPORARY_REDIRECT
                                ? RequestBuilder.copy(request).setUri(uri).build()
                                : new HttpGet(uri);
            }
        }
    
    }    
    

    DefaultRedirectStrategy实现了RedirectStrategy接口,它定义了redirectMethods,默认是Get和Head;isRedirected方法先获取response的statusCode,对于302需要location的header有值且请求method在redirectMethods中(isRedirectable),对于301及307仅仅判断isRedirectable,对于303返回true,其余的返回false

    getRedirect方法先通过getLocationURI获取目标地址,然后针对get或者head分别构造HttpHead及HttpGet,剩下的根据statusCode判断,是307则拷贝原来的request,否则返回HttpGet

    RedirectExec

    org/apache/http/impl/execchain/RedirectExec.java

    /**
     * Request executor in the request execution chain that is responsible
     * for handling of request redirects.
     * <p>
     * Further responsibilities such as communication with the opposite
     * endpoint is delegated to the next executor in the request execution
     * chain.
     * </p>
     *
     * @since 4.3
     */
    @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
    public class RedirectExec implements ClientExecChain {
    
        private final Log log = LogFactory.getLog(getClass());
    
        private final ClientExecChain requestExecutor;
        private final RedirectStrategy redirectStrategy;
        private final HttpRoutePlanner routePlanner;
    
        public RedirectExec(
                final ClientExecChain requestExecutor,
                final HttpRoutePlanner routePlanner,
                final RedirectStrategy redirectStrategy) {
            super();
            Args.notNull(requestExecutor, "HTTP client request executor");
            Args.notNull(routePlanner, "HTTP route planner");
            Args.notNull(redirectStrategy, "HTTP redirect strategy");
            this.requestExecutor = requestExecutor;
            this.routePlanner = routePlanner;
            this.redirectStrategy = redirectStrategy;
        }
    
        @Override
        public CloseableHttpResponse execute(
                final HttpRoute route,
                final HttpRequestWrapper request,
                final HttpClientContext context,
                final HttpExecutionAware execAware) throws IOException, HttpException {
            Args.notNull(route, "HTTP route");
            Args.notNull(request, "HTTP request");
            Args.notNull(context, "HTTP context");
    
            final List<URI> redirectLocations = context.getRedirectLocations();
            if (redirectLocations != null) {
                redirectLocations.clear();
            }
    
            final RequestConfig config = context.getRequestConfig();
            final int maxRedirects = config.getMaxRedirects() > 0 ? config.getMaxRedirects() : 50;
            HttpRoute currentRoute = route;
            HttpRequestWrapper currentRequest = request;
            for (int redirectCount = 0;;) {
                final CloseableHttpResponse response = requestExecutor.execute(
                        currentRoute, currentRequest, context, execAware);
                try {
                    if (config.isRedirectsEnabled() &&
                            this.redirectStrategy.isRedirected(currentRequest.getOriginal(), response, context)) {
    
                        if (redirectCount >= maxRedirects) {
                            throw new RedirectException("Maximum redirects ("+ maxRedirects + ") exceeded");
                        }
                        redirectCount++;
    
                        final HttpRequest redirect = this.redirectStrategy.getRedirect(
                                currentRequest.getOriginal(), response, context);
                        if (!redirect.headerIterator().hasNext()) {
                            final HttpRequest original = request.getOriginal();
                            redirect.setHeaders(original.getAllHeaders());
                        }
                        currentRequest = HttpRequestWrapper.wrap(redirect);
    
                        if (currentRequest instanceof HttpEntityEnclosingRequest) {
                            RequestEntityProxy.enhance((HttpEntityEnclosingRequest) currentRequest);
                        }
    
                        final URI uri = currentRequest.getURI();
                        final HttpHost newTarget = URIUtils.extractHost(uri);
                        if (newTarget == null) {
                            throw new ProtocolException("Redirect URI does not specify a valid host name: " +
                                    uri);
                        }
    
                        // Reset virtual host and auth states if redirecting to another host
                        if (!currentRoute.getTargetHost().equals(newTarget)) {
                            final AuthState targetAuthState = context.getTargetAuthState();
                            if (targetAuthState != null) {
                                this.log.debug("Resetting target auth state");
                                targetAuthState.reset();
                            }
                            final AuthState proxyAuthState = context.getProxyAuthState();
                            if (proxyAuthState != null && proxyAuthState.isConnectionBased()) {
                                this.log.debug("Resetting proxy auth state");
                                proxyAuthState.reset();
                            }
                        }
    
                        currentRoute = this.routePlanner.determineRoute(newTarget, currentRequest, context);
                        if (this.log.isDebugEnabled()) {
                            this.log.debug("Redirecting to '" + uri + "' via " + currentRoute);
                        }
                        EntityUtils.consume(response.getEntity());
                        response.close();
                    } else {
                        return response;
                    }
                } catch (final RuntimeException ex) {
                    response.close();
                    throw ex;
                } catch (final IOException ex) {
                    response.close();
                    throw ex;
                } catch (final HttpException ex) {
                    // Protocol exception related to a direct.
                    // The underlying connection may still be salvaged.
                    try {
                        EntityUtils.consume(response.getEntity());
                    } catch (final IOException ioex) {
                        this.log.debug("I/O error while releasing connection", ioex);
                    } finally {
                        response.close();
                    }
                    throw ex;
                }
            }
        }
    
    }
    

    RedirectExec实现了ClientExecChain接口,其构造器要求传入requestExecutor、redirectStrategy、routePlanner,其execute方法会先获取maxRedirects参数,然后执行requestExecutor.execute,接着在config.isRedirectsEnabled()以及redirectStrategy.isRedirected为true时才进入redirect逻辑,它会先判断是否超出maxRedirects,大于等于则抛出RedirectException,否则通过redirectStrategy.getRedirect获取HttpRequest,更新currentRoute,然后清理entity关闭response继续下次循环,即执行redirect逻辑。

    小结

    HttpClient的RedirectStrategy定义了两个方法,一个是是否需要redirect,一个是获取redirect的请求,DefaultRedirectStrategy的构造器支持传入redirectMethods,默认是Get和Head,isRedirected方法主要是对302,301,307,303进行了判断,getRedirect方法主要是通过location获取目标地址,然后根据原来的method和statusCode构造HttpUriRequest。

    相关文章

      网友评论

          本文标题:聊聊HttpClient的RedirectStrategy

          本文链接:https://www.haomeiwen.com/subject/sbifidtx.html