Spring - Повторить запрос, если служба возвращает 409 HTTP-код
У меня есть приложение Spring + CXF, которое использует API передачи: Передача RPC работает на другом сервере.
В соответствии с документами передачи вам необходимо отправить токен, который генерируется по первому запросу. Затем сервер отвечает 409 http-кодом вместе с заголовком, содержащим токен. Этот токен должен быть отправлен на все последующие вызовы:
2.3.1. Защита CSRF. Большинство серверов RPC с передачей требуют, чтобы заголовок X-Transmission-Session-Id отправлялся с запросами, чтобы предотвратить Атаки CSRF. Когда ваш запрос имеет неправильный идентификатор - например, когда вы отправьте свой первый запрос или когда на сервере истекает токен CSRF - сервер RPC передачи вернет ошибку HTTP 409 с правой X-Transmission-Session-Id в своих собственных заголовках. Итак, правильный способ обработки ответа 409 - это обновить X-Transmission-Session-Id и повторно отправить предыдущий запрос.
Я искал решение, используя фильтр CXF или перехватчик, который в основном будет обрабатывать ответ 409 и повторить первоначальный запрос, добавляя заголовок маркера. Я думаю, что клиенты могут сохранить этот токен и отправить его в будущих вызовах.
Я не очень хорошо знаком с cxf, поэтому мне было интересно, можно ли это сделать и как. Любые подсказки были бы полезны.
Спасибо!
Ответы
Ответ 1
Здесь spring-retry, который теперь является независимым проектом и больше не является частью spring -batch.
Как объяснено здесь повторный вызов повторного запроса поможет сделать другой вызов обновленным с заголовком токена.
Псевдокод/логика в этом случае будет выглядеть примерно как
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
с разумными политиками повтора и отсрочки, чтобы избежать конфликтов/сюрпризов ресурсов.
Знайте в комментариях в случае любых запросов /roadblock.
NB: RetryContext
реализация RetryContextSupport
имеет метод hasAttribute
и setAttribute
, унаследованный от Spring core AttributeAccessor
Ответ 2
Предполагая, что вы используете Apache CXF JAX RS Client, это легко сделать, просто создав пользовательское исключение времени выполнения и 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;
}
}