如何自定义OpenFeign实现带请求体的POST请求重定向?
问题描述
使用OpenFeign发送带请求体的POST请求到返回HTTP 302状态码的服务器时,会抛出HttpRetryException: cannot retry due to redirection, in streaming mode错误;但POST请求不带请求体时,Feign可正常跟随重定向。该问题由sun.net.www.protocol.http.HttpURLConnection的限制导致,它默认阻止带请求体的POST请求执行重定向。
解决方案
1. 替换默认HttpClient为Apache HttpClient或OkHttp
Feign默认使用JDK的HttpURLConnection,可替换为支持带请求体重定向的第三方客户端:
以Apache HttpClient为例:
首先引入Maven依赖:
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> <version>对应Feign版本</version> </dependency>
修改Feign配置,使用ApacheHttpClient:
@Configuration public class FeignConfiguration { @Bean public FeignPostRedirectClient feignPostRedirectClient() { return Feign.builder() .logLevel(Logger.Level.FULL) .logger(new Slf4jLogger()) .encoder(new JacksonEncoder()) .decoder(new JacksonDecoder()) .client(new ApacheHttpClient()) // 替换为Apache HttpClient .options(new Request.Options(10, TimeUnit.SECONDS, 10, TimeUnit.SECONDS, true)) .target(FeignPostRedirectClient.class, "http://localhost:8082"); } }
Apache HttpClient默认支持处理带请求体的POST重定向,会按HTTP规范自动处理请求方法转换(跨域重定向时POST转GET,同域则保持POST),也可自定义配置。
2. 自定义Feign Client手动处理重定向
若不想替换HttpClient,可自定义Feign Client捕获302状态码,手动重新发送请求:
public class CustomFeignClient implements Client { private final Client delegate; public CustomFeignClient(Client delegate) { this.delegate = delegate; } @Override public Response execute(Request request, Request.Options options) throws IOException { Response response = delegate.execute(request, options); // 处理302重定向 if (response.status() == 302) { String location = response.headers().get("Location").iterator().next(); // 构建新请求,可根据业务需求选择保持POST或转为GET Request newRequest = Request.create(request.method(), location, request.headers(), request.body(), request.charset()); return delegate.execute(newRequest, options); } return response; } }
在Feign配置中使用该自定义Client:
@Configuration public class FeignConfiguration { @Bean public FeignPostRedirectClient feignPostRedirectClient() { Client defaultClient = new Client.Default(null, null); return Feign.builder() .logLevel(Logger.Level.FULL) .logger(new Slf4jLogger()) .encoder(new JacksonEncoder()) .decoder(new JacksonDecoder()) .client(new CustomFeignClient(defaultClient)) // 使用自定义Client .options(new Request.Options(10, TimeUnit.SECONDS, 10, TimeUnit.SECONDS, true)) .target(FeignPostRedirectClient.class, "http://localhost:8082"); } }
注意:手动处理时需遵循HTTP规范,RFC规定跨域POST重定向应转为GET,若业务需保持POST可自定义逻辑。
3. 调整服务器端重定向逻辑(若可控)
若有权限修改服务器,可将302改为307或308状态码:
- 307:临时重定向,强制保持原请求方法和请求体
- 308:永久重定向,强制保持原请求方法和请求体
JDK的HttpURLConnection支持这两个状态码的重定向,不会抛出上述错误。
原问题相关代码
Feign客户端接口
public interface FeignPostRedirectClient { @RequestLine("POST /redirect-server") void sendPostRequestWithBodyToRedirectServer(Map<String, ?> formBodyParams); // 重定向失败 //void sendPostRequestWithBodyToRedirectServer(); // 重定向成功 }
Feign配置类
@Configuration public class FeignConfiguration { @Bean public FeignPostRedirectClient feignPostRedirectClient() { return Feign.builder() .logLevel(Logger.Level.FULL) .logger(new Slf4jLogger()) .encoder(new JacksonEncoder()) .decoder(new JacksonDecoder()) .options(new Request.Options(10, TimeUnit.SECONDS, 10, TimeUnit.SECONDS, true)) .target(FeignPostRedirectClient.class, "http://localhost:8082"); } }
调用代码
Map<String, String> formParams = new HashMap<>(); formParams.put("Username", "alice"); formParams.put("Password", "pass"); feignPostRedirectClient.sendPostRequestWithBodyToRedirectServer(formParams);
错误日志
2022-11-21 21:53:49.880 DEBUG 3944 --- [ main] feign.Logger
: [FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer]
---> POST http://localhost:8082/redirect-server HTTP/1.1 2022-11-21 21:53:49.880 DEBUG 3944 --- [ main] feign.Logger
: [FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer]
Content-Length: 49 2022-11-21 21:53:49.880 DEBUG 3944 --- [
main] feign.Logger :
[FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer]
2022-11-21 21:53:49.880 DEBUG 3944 --- [ main] feign.Logger
: [FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer] {
"Username" : "alice", "Password" : "pass" } 2022-11-21 21:53:49.881
DEBUG 3944 --- [ main] feign.Logger
: [FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer]
---> END HTTP (49-byte body) 2022-11-21 21:53:54.368 DEBUG 3944 --- [ main] feign.Logger :
[FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer] <---
ERROR HttpRetryException: cannot retry due to redirection, in
streaming mode (4485ms) 2022-11-21 21:53:54.371 DEBUG 3944 --- [
main] feign.Logger :
[FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer]
java.net.HttpRetryException: cannot retry due to redirection, in
streaming mode at
sun.net.www.protocol.http.HttpURLConnection.followRedirect0(HttpURLConnection.java:2682)
at
sun.net.www.protocol.http.HttpURLConnection.followRedirect(HttpURLConnection.java:2668)
at
sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1847)
at
sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1505)
at
java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
at feign.Client$Default.convertResponse(Client.java:110) at
feign.Client$Default.execute(Client.java:106) at
feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:102)
at
feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:72)
at
feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:109)
at
com.sun.proxy.$Proxy71.sendPostRequestWithBodyToRedirectServer(Unknown
Source) at
com.kingsoo.online.store.service.OnlineStoreServiceApplication.run(OnlineStoreServiceApplication.java:28)
at
org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:771)
at
org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:755)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:1306)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:1295)
at
com.kingsoo.online.store.service.OnlineStoreServiceApplication.main(OnlineStoreServiceApplication.java:19)2022-11-21 21:53:54.371 DEBUG 3944 --- [ main] feign.Logger
: [FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer]
<--- END ERROR 2022-11-21 21:53:54.588 DEBUG 3944 --- [
main] feign.Logger :
[FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer] --->
RETRYING 2022-11-21 21:53:54.589 DEBUG 3944 --- [ main]
feign.Logger :
[FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer] --->
POST http://localhost:8082/redirect-server HTTP/1.1 2022-11-21
21:53:54.591 DEBUG 3944 --- [ main] feign.Logger
: [FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer]
Content-Length: 49 2022-11-21 21:53:54.591 DEBUG 3944 --- [
main] feign.Logger :
[FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer]
2022-11-21 21:53:54.592 DEBUG 3944 --- [ main] feign.Logger
: [FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer] {
"Username" : "alice", "Password" : "pass" } 2022-11-21 21:53:54.592
DEBUG 3944 --- [ main] feign.Logger
: [FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer]
---> END HTTP (49-byte body) 2022-11-21 21:54:54.606 DEBUG 3944 --- [ main] feign.Logger :
[FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer] <---
ERROR SocketTimeoutException: Read timed out (60010ms) 2022-11-21
21:54:54.609 DEBUG 3944 --- [ main] feign.Logger
: [FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer]
java.net.SocketTimeoutException: Read timed out at
java.net.SocketInputStream.socketRead0(Native Method) at
java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at
java.net.SocketInputStream.read(SocketInputStream.java:171) at
java.net.SocketInputStream.read(SocketInputStream.java:141) at
java.io.BufferedInputStream.fill(BufferedInputStream.java:246) at
java.io.BufferedInputStream.read1(BufferedInputStream.java:286) at
java.io.BufferedInputStream.read(BufferedInputStream.java:345) at
sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:743) at
sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:678) at
sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1600)
at
sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1505)
at
java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
at feign.Client$Default.convertResponse(Client.java:110) at
feign.Client$Default.execute(Client.java:106) at
feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:102)
at
feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:72)
at
feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:109)
at
com.sun.proxy.$Proxy71.sendPostRequestWithBodyToRedirectServer(Unknown
Source) at
com.kingsoo.online.store.service.OnlineStoreServiceApplication.run(OnlineStoreServiceApplication.java:28)
at
org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:771)
at
org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:755)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:1306)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:1295)
at
com.kingsoo.online.store.service.OnlineStoreServiceApplication.main(OnlineStoreServiceApplication.java:19)2022-11-21 21:54:54.609 DEBUG 3944 --- [ main] feign.Logger
: [FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer]
<--- END ERROR 2022-11-21 21:54:54.840 DEBUG 3944 --- [
main] feign.Logger :
[FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer] --->
RETRYING 2022-11-21 21:54:54.842 DEBUG 3944 --- [ main]
feign.Logger :
[FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer] --->
POST http://localhost:8082/redirect-server HTTP/1.1 2022-11-21
21:54:54.842 DEBUG 3944 --- [ main] feign.Logger
: [FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer]
Content-Length: 49 2022-11-21 21:54:54.842 DEBUG 3944 --- [
main] feign.Logger :
[FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer]
2022-11-21 21:54:54.842 DEBUG 3944 --- [ main] feign.Logger
: [FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer] {
"Username" : "alice", "Password" : "pass" } 2022-11-21 21:54:54.842
DEBUG 3944 --- [ main] feign.Logger
: [FeignPostRedirectClient#sendPostRequestWithBodyToRedirectServer]
---> END HTTP (49-byte body)
内容的提问来源于stack exchange,提问作者user1726366

