Spring - 如果服务返回409 HTTP代码

时间:2017-01-18 04:00:15

标签: java spring cxf-client

我有一个Spring + CXF应用程序,它使用传输API:Transmission RPC在另一台服务器上运行。

根据Transmission文档,您需要发送在第一个请求时生成的令牌。然后,服务器使用409 http代码以及包含令牌的标头进行响应。此令牌应在所有后续呼叫中发送:

  

2.3.1。 CSRF保护大多数传输RPC服务器都需要一个X-Transmission-Session-Id标头与请求一起发送,以防止   CSRF攻击。当您的请求有错误的ID时 - 例如当您   发送您的第一个请求,或者当服务器到期CSRF令牌时 -   传输RPC服务器将返回HTTP 409错误   正确的X-Transmission-Session-Id在它自己的标题中。所以,正确的   处理409响应的方法是更新你的   X-Transmission-Session-Id并重新发送先前的请求。

我正在寻找使用CXF过滤器或拦截器的解决方案,它基本上将处理409响应并重试添加令牌标头的初始请求。我认为客户可以保留此令牌并在以后的呼叫中发送它。

我对cxf不是很熟悉所以我想知道这是否可以实现以及如何实现。任何提示都会有所帮助。

谢谢!

2 个答案:

答案 0 :(得分:2)

这里可以使用spring-retry,它现在是一个独立的项目,不再是春季批次的一部分。

正如解释here重试回调将有助于使用令牌标头更新另一个调用。

在这种情况下,伪代码/逻辑看起来像下面的

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配置合理的重试&amp;退避政策,以避免任何资源争用/意外。

如果有任何疑问/包版,请在评论中说明。

N.B。RetryContext的实施RetryContextSupporthasAttribute&amp;从Spring核心AttributeAccessor继承的setAttribute方法

答案 1 :(得分:1)

假设您正在使用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;
    }
}