请求的重定向

项目中使用 Feign 调用 HTTP API 时,出现一个错误:HttpRetryException: cannot retry due to redirection, in streaming mode

feign.RetryableException: cannot retry due to redirection, in streaming mode executing POST <api_url>
    at feign.FeignException.errorExecuting(FeignException.java:67)
    ……    
Caused by: java.net.HttpRetryException: cannot retry due to redirection, in streaming mode
    at sun.net.www.protocol.http.HttpURLConnection.followRedirect0(Unknown Source)
    at sun.net.www.protocol.http.HttpURLConnection.followRedirect(Unknown Source)
  ……

由于搜索不到解决方案,自己用Spring MVC 写了一个简单的带有 redirect API,使用feign请求,没有任何问题。首先怀疑是 API 方 (使用python编写)的问题。

于是分别使用 postman 和  JavaScript ajax 调用该接口,发现没有问题。结合异常的意思:“无法在流模式下使用重定向”。

那么问题就出在 Feign 使用的底层 http client 框架,对于重定向的处理策略不同。Feign 默认使用 java.net.HttpURLConnection。更改为 okhttp3 后,正常。于是猜想:

不同的http client 对于重定向的判定策略不同。

接下来根据源码验证了这一猜想。

不同的http client 对于重定向的判定策略不同。

通过排查来验证结论:

一、HttpURLConnection 的重定向策略

通过跟踪 sun.net.www.protocol.http.HttpURLConnection 的源码(followRedirect方法),发现一些重定向的规则:

响应状态码码必须是3xx
响应头必须包含Location
location中url的协议与原来的url协议相同。
非流模式。(如果使用流模式,直接抛出上面的异常)
……
如果通过body发送参数,请求就会是流模式,直接抛出异常。在自己的模拟接口中加上 RequestBody 错误复现。

说明:

1、sun.net.www.protocol.http.HttpURLConnection 是 java.net.HttpURLConnection 子类。

2、使用JDK1.8。

附上部分源码(反编译的,命名比较差)

    /*
     * 判断是否追踪重定向,如果返回 true则执行根据响应头中的Location请求重定向。返回 false 不执行操作。
     */
    private boolean followRedirect() throws IOException {
        URL localURL1;
        // 判断实例的设置
        if (!(getInstanceFollowRedirects())) {
            return false;
        }
 
        // 响应码 300 301 302 303 305 307 追踪重定向
        int i = getResponseCode();
        if ((i < 300) || (i > 307) || (i == 306) || (i == 304)) {
            return false;
        }
        // 判断响应头是否包含 Location
        String str = getHeaderField("Location");
        if (str == null) {
            return false;
        }
 
        try {
            // 重定向的url的协议与原url的协议是否相同(HTTP或HTTPS)
            localURL1 = new URL(str);
            if (!(this.url.getProtocol().equalsIgnoreCase(localURL1.getProtocol())))
                return false;
 
        } catch (MalformedURLException localMalformedURLException) {
            localURL1 = new URL(this.url, str);
        }
 
        /* 判断是否有权限访问重定向的URL */
        URL localURL2 = localURL1;
        this.socketPermission = null;
        SocketPermission localSocketPermission = URLtoSocketPermission(localURL1);
 
        if (localSocketPermission != null)
            try {
                return ((Boolean) AccessController
                        .doPrivilegedWithCombiner(new PrivilegedExceptionAction(this, str, i, localURL2) {
                            public Boolean run() throws IOException {
                                return Boolean.valueOf(HttpURLConnection.access$300(this.this$0, this.val$loc,
                                        this.val$stat, this.val$locUrl0));
                            }
                        }, null, new Permission[] { localSocketPermission })).booleanValue();
            } catch (PrivilegedActionException localPrivilegedActionException) {
                throw ((IOException) localPrivilegedActionException.getException());
            }
        // 进一步判断
        return followRedirect0(str, i, localURL1);
    }
/*
     * 进一步判断是否追踪重定向
     */
private boolean followRedirect0(String paramString, int paramInt, URL paramURL) throws IOException {
        disconnectInternal();
        
        /*流模式下,不能进行重定向。(只要有请求体就是流模式)*/
        if (streaming())
            throw new HttpRetryException("cannot retry due to redirection, in streaming mode", paramInt, paramString);
        }
…………
        return true;
    }
 

二、Apache HttpClient 的重定向规则

跟踪源码,HttpClient 的重定向规则十分清晰,扩展性强。重定向实现逻辑位于 org.apache.http.impl.execchain.RedirectExec.execute 方法,首先判断配置是否允许重定向然后交给org.apache.http.client.RedirectStrategy 判断。

 public CloseableHttpResponse execute( final HttpRoute route,  final HttpRequestWrapper request, final HttpClientContext context,  final HttpExecutionAware execAware) throws IOException, HttpException {
      
         //首先判断配置中是否孕育重定向
       if (config.isRedirectsEnabled() &&
        // 接着将重定向判定权交给  RedirectStrategy 来完成      
        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);
                   
        }
    }
默认策略 org.apache.http.impl.client.DefaultRedirectStrategy。规则很简单,只允许 GET 和 HEAD 请求重定向。

     /**
     * Redirectable methods.
     */
    private static final String[] REDIRECT_METHODS = new String[] {  HttpGet.METHOD_NAME, HttpHead.METHOD_NAME };
 
 
     /**
     * @since 4.2
     */
    protected boolean isRedirectable(final String method) {
        for (final String m: REDIRECT_METHODS) {
            if (m.equalsIgnoreCase(method)) {
                return true;
            }
        }
        return false;
    }
 

说明: 使用 httpclient-4.5.3

三、okhttp 的重定向规则

尚未跟踪源码,从表现来看,okhttp的重定向归则最为宽松。

总结

遇到调用重定向的问题,如果 postman 调用没问题,可以跟踪一下所使用 http client 的重定向归则。
 

猜你喜欢

转载自blog.csdn.net/weixin_41492331/article/details/88735574
今日推荐