关于重定向那些事(Java 使用http client 调用带有Redirect 的坑)

项目中使用 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方法),发现一些重定向的规则:

  1. 响应状态码码必须是3xx
  2. 响应头必须包含Location
  3. location中url的协议与原来的url协议相同。
  4. 非流模式。(如果使用流模式,直接抛出上面的异常)
  5. ……

如果通过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 的重定向归则。

<think>好的,我现在需要回答用户关于Java中如何实现URL重定向的问题,特别是方法及示例。首先,我需要回忆一下Java中处理重定向的常见方式。 用户提到了POST请求的重定向示例,可能涉及HTTP协议中的重定向机制。HTTP重定向通常通过状态码3xx来实现,比如302临时重定向。在Java中,处理重定向可以分为服务端和客户端两种情况。服务端重定向可以通过HttpServletResponse的sendRedirect方法,而客户端可能需要处理自动重定向或者手动处理。 另外,用户提供的引用内容中有一个sendByPost方法的示例,使用HttpURLConnection,并且提到要允许重定向。这里可能需要设置setInstanceFollowRedirects为true,或者手动处理重定向。需要确认HttpURLConnection默认是否自动跟随重定向,我记得默认是自动的,但POST请求的重定向可能会被转换为GET,这可能导致问题,因此需要特别注意。 接下来,我需要整理服务端和客户端的不同方法。服务端重定向比较简单,直接调用sendRedirect即可。客户端方面,使用HttpURLConnection时,可能需要禁用自动重定向并手动处理,尤其是在POST请求需要保持方法的情况下。另外,新的HttpClientJava 11+)提供了更灵活的方式,可以设置重定向策略。 还需要注意,不同的HTTP库可能有不同的行为,比如Apache HttpClient可能需要配置重定向策略。示例代码需要展示这两种情况:自动跟随重定向和手动处理。同时,要提到POST重定向时的潜在问题,比如方法变更和数据丢失。 最后,生成相关问题时要覆盖重定向类型、处理POST重定向的注意项、禁止自动重定向的方法以及不同Java版本的处理方式等。</think>在Java中实现URL重定向主要有服务端和客户端两种方式。以下是具体方法和示例说明: ### 一、服务端重定向HTTP响应重定向使用`HttpServletResponse`的`sendRedirect`方法实现302临时重定向: ```java // Spring MVC示例 @GetMapping("/old-url") public void redirect(HttpServletResponse response) throws IOException { response.sendRedirect("https://example.com/new-url"); } ``` 该方法通过设置`Location`响应头和状态码302实现跳转[^1]。 ### 二、客户端重定向处理 1. **自动跟随重定向**(默认行为) ```java // 使用HttpURLConnection自动处理重定向 URL obj = new URL("http://example.com"); HttpURLConnection conn = (HttpURLConnection) obj.openConnection(); conn.setRequestMethod("POST"); conn.setInstanceFollowRedirects(true); // 默认已开启 ``` 2. **手动处理重定向**(禁用自动跳转) ```java conn.setInstanceFollowRedirects(false); // 禁用自动重定向 int status = conn.getResponseCode(); if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM) { String newUrl = conn.getHeaderField("Location"); // 手动发起新请求 HttpURLConnection newConn = (HttpURLConnection) new URL(newUrl).openConnection(); newConn.setRequestMethod("GET"); // 注意方法可能被修改 } ``` ### 三、POST请求重定向注意项 当POST请求触发重定向时: 1. 浏览器/客户端默认会将POST转为GET请求 2. 原始请求体数据会丢失 3. 需要保持POST方法时应使用307状态码: ```java // 服务端设置307状态码 response.setStatus(307); response.setHeader("Location", "/new-url"); ``` ### 四、Java 11+ HttpClient示例 ```java HttpClient client = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.ALWAYS) .build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://example.com")) .POST(HttpRequest.BodyPublishers.ofString("data")) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值