Spring - 如果服务 returns 409 HTTP 代码则重试请求
Spring - Retry request if service returns 409 HTTP Code
我有一个 Spring + CXF 应用程序,它在另一台服务器上使用传输 API: Transmission RPC 运行。
根据 Transmission 文档,您需要发送一个在第一次请求时生成的令牌。然后服务器响应 409 http 代码以及包含令牌的 header。此令牌应在所有后续调用中发送:
2.3.1. CSRF Protection Most Transmission RPC servers require a X-Transmission-Session-Id header to be sent with requests, to prevent
CSRF attacks. When your request has the wrong id -- such as when you
send your first request, or when the server expires the CSRF token --
the Transmission RPC server will return an HTTP 409 error with the
right X-Transmission-Session-Id in its own headers. So, the correct
way to handle a 409 response is to update your
X-Transmission-Session-Id and to resend the previous request.
我正在寻找使用 CXF 过滤器或拦截器的解决方案,它基本上将处理 409 响应并重试添加令牌的初始请求 header。我认为客户可以保留此令牌并在以后的调用中发送它。
我对cxf不是很熟悉所以我想知道这是否可以完成以及如何完成。任何提示都会有所帮助。
谢谢!
这里可以使用spring-retry,现在是一个独立的项目,不再是spring-batch的一部分。
正如所解释的那样 here 重试回调将有助于使用令牌更新另一个调用 header。
这种情况下的伪代码/逻辑如下所示
RetryTemplate template = new RetryTemplate();
Foo foo = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
/*
* 1. Check if RetryContext contains the token via hasAttribute. If available set the header else proceed
* 2. Call the transmission API
* 3.a. If API responds with 409, read the token
* 3.a.1. Store the token in RetryContext via setAttribute method
* 3.a.2. Throw a custom exception so that retry kicks in
* 3.b. If API response is non 409 handle according to business logic
* 4. Return result
*/
}
});
确保使用合理的重试和退避策略配置 RetryTemplate
以避免任何资源争用/意外。
如有任何疑问/障碍,请在评论中告知。
N.B.:RetryContext
的实现 RetryContextSupport
has the hasAttribute
& setAttribute
method inherited from Spring core AttributeAccessor
假设您使用的是 Apache CXF JAX RS 客户端,只需创建自定义运行时异常并为其创建 ResponseExceptionMapper
即可轻松实现。所以想法是手动将 409 结果转换为一些异常,然后正确处理它们(在您的情况下重试服务调用)。
请参阅以下代码片段以获得完整的工作示例。
@SpringBootApplication
@EnableJaxRsProxyClient
public class SpringBootClientApplication {
// This can e stored somewhere in db or elsewhere
private static String lastToken = "";
public static void main(String[] args) {
SpringApplication.run(SpringBootClientApplication.class, args);
}
@Bean
CommandLineRunner initWebClientRunner(final TransmissionService service) {
return new CommandLineRunner() {
@Override
public void run(String... runArgs) throws Exception {
try {
System.out.println(service.sayHello(1, lastToken));
// catch the TokenExpiredException get the new token and retry
} catch (TokenExpiredException ex) {
lastToken = ex.getNewToken();
System.out.println(service.sayHello(1, lastToken));
}
}
};
}
public static class TokenExpiredException extends RuntimeException {
private String newToken;
public TokenExpiredException(String token) {
newToken = token;
}
public String getNewToken() {
return newToken;
}
}
/**
* This is where the magic is done !!!!
*/
@Provider
public static class TokenExpiredExceptionMapper implements ResponseExceptionMapper<TokenExpiredException> {
@Override
public TokenExpiredException fromResponse(Response r) {
if (r.getStatus() == 409) {
return new TokenExpiredException(r.getHeaderString("X-Transmission-Session-Id"));
}
return null;
}
}
@Path("/post")
public interface TransmissionService {
@GET
@Path("/{a}")
@Produces(MediaType.APPLICATION_JSON_VALUE)
String sayHello(@PathParam("a") Integer a, @HeaderParam("X-Transmission-Session-Id") String sessionId)
throws TokenExpiredException;
}
}
我有一个 Spring + CXF 应用程序,它在另一台服务器上使用传输 API: Transmission RPC 运行。
根据 Transmission 文档,您需要发送一个在第一次请求时生成的令牌。然后服务器响应 409 http 代码以及包含令牌的 header。此令牌应在所有后续调用中发送:
2.3.1. CSRF Protection Most Transmission RPC servers require a X-Transmission-Session-Id header to be sent with requests, to prevent CSRF attacks. When your request has the wrong id -- such as when you send your first request, or when the server expires the CSRF token -- the Transmission RPC server will return an HTTP 409 error with the right X-Transmission-Session-Id in its own headers. So, the correct way to handle a 409 response is to update your X-Transmission-Session-Id and to resend the previous request.
我正在寻找使用 CXF 过滤器或拦截器的解决方案,它基本上将处理 409 响应并重试添加令牌的初始请求 header。我认为客户可以保留此令牌并在以后的调用中发送它。
我对cxf不是很熟悉所以我想知道这是否可以完成以及如何完成。任何提示都会有所帮助。
谢谢!
这里可以使用spring-retry,现在是一个独立的项目,不再是spring-batch的一部分。
正如所解释的那样 here 重试回调将有助于使用令牌更新另一个调用 header。
这种情况下的伪代码/逻辑如下所示
RetryTemplate template = new RetryTemplate();
Foo foo = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
/*
* 1. Check if RetryContext contains the token via hasAttribute. If available set the header else proceed
* 2. Call the transmission API
* 3.a. If API responds with 409, read the token
* 3.a.1. Store the token in RetryContext via setAttribute method
* 3.a.2. Throw a custom exception so that retry kicks in
* 3.b. If API response is non 409 handle according to business logic
* 4. Return result
*/
}
});
确保使用合理的重试和退避策略配置 RetryTemplate
以避免任何资源争用/意外。
如有任何疑问/障碍,请在评论中告知。
N.B.:RetryContext
的实现 RetryContextSupport
has the hasAttribute
& setAttribute
method inherited from Spring core AttributeAccessor
假设您使用的是 Apache CXF JAX RS 客户端,只需创建自定义运行时异常并为其创建 ResponseExceptionMapper
即可轻松实现。所以想法是手动将 409 结果转换为一些异常,然后正确处理它们(在您的情况下重试服务调用)。
请参阅以下代码片段以获得完整的工作示例。
@SpringBootApplication
@EnableJaxRsProxyClient
public class SpringBootClientApplication {
// This can e stored somewhere in db or elsewhere
private static String lastToken = "";
public static void main(String[] args) {
SpringApplication.run(SpringBootClientApplication.class, args);
}
@Bean
CommandLineRunner initWebClientRunner(final TransmissionService service) {
return new CommandLineRunner() {
@Override
public void run(String... runArgs) throws Exception {
try {
System.out.println(service.sayHello(1, lastToken));
// catch the TokenExpiredException get the new token and retry
} catch (TokenExpiredException ex) {
lastToken = ex.getNewToken();
System.out.println(service.sayHello(1, lastToken));
}
}
};
}
public static class TokenExpiredException extends RuntimeException {
private String newToken;
public TokenExpiredException(String token) {
newToken = token;
}
public String getNewToken() {
return newToken;
}
}
/**
* This is where the magic is done !!!!
*/
@Provider
public static class TokenExpiredExceptionMapper implements ResponseExceptionMapper<TokenExpiredException> {
@Override
public TokenExpiredException fromResponse(Response r) {
if (r.getStatus() == 409) {
return new TokenExpiredException(r.getHeaderString("X-Transmission-Session-Id"));
}
return null;
}
}
@Path("/post")
public interface TransmissionService {
@GET
@Path("/{a}")
@Produces(MediaType.APPLICATION_JSON_VALUE)
String sayHello(@PathParam("a") Integer a, @HeaderParam("X-Transmission-Session-Id") String sessionId)
throws TokenExpiredException;
}
}