#java #servlet-filters
#java #сервлет-фильтры
Вопрос:
Я разрабатываю простое приложение Spring Boot с фильтром сервлетов, предназначенное для установки кода состояния ответа:
@Component
public class TestFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) resp;
response.setStatus(201);
chain.doFilter(req, resp);
}
Все идет как ожидалось (статус 201), если контроллер возвращает строку. Но если контроллер возвращает ResponseEntity, то после вызова doFilter() код состояния равен 200 вместо 201:
@RestController
public class TestController {
@GetMapping("/string")
public String testString() {
return "OK"; // status code is 201 as set by Filter
}
}
@RestController
public class TestController {
@GetMapping("/entity")
public ResponseEntity<String> testResponseEntity() {
return ResponseEntity.ok("OK"); // status code is 200
}
}
Почему фильтр не изменяет код состояния при использовании ResponseEntity?
—
Проект на Github: https://github.com/timofeev-denis/set-status-code
Ответ №1:
С /string
конечной точкой вы не изменяете код состояния. С /entity
конечной точкой вы явно (вы устанавливаете ее равной 200 на основании возврата ok).
Ваша реализация фильтра изменяет код состояния ответа и затем переходит к запуску остальной цепочки фильтров — и затем сервлета. И мы только что установили, что сервлет (ваш контроллер) устанавливает код состояния ответа на что-то другое.
Итак, вам нужно изменить код ответа после того, как сервлет / контроллер сделал свое дело.
Вашей первой мыслью может быть переопределение вашего фильтра следующим образом:
@Component
public class TestFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(req, resp);
} finally {
HttpServletResponse response = (HttpServletResponse) resp;
response.setStatus(205);
}
}
}
Но, к сожалению, это тоже не сработает! Согласно документации:
Обратите внимание, что postHandle менее полезен с методами @ResponseBody и ResponseEntity, для которых ответ записывается и фиксируется в HandlerAdapter и перед postHandle. Это означает, что слишком поздно вносить какие-либо изменения в ответ, такие как добавление дополнительного заголовка. Для таких сценариев вы можете реализовать ResponseBodyAdvice и либо объявить его как компонент рекомендаций контроллера, либо настроить его непосредственно в RequestMappingHandlerAdapter.
Это может дать желаемый эффект, который вы ищете (обратите внимание, что я установил значение «КОНТРОЛЬНАЯ ТОЧКА» просто для демонстрации сути!):
@ControllerAdvice
public class TestResponseBodyAdvice<T> implements ResponseBodyAdvice<T> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public T beforeBodyWrite(T body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//
// insert status code of choice here
response.setStatusCode(HttpStatus.CHECKPOINT);
return body;
}
}
Комментарии:
1. Ты спасаешь жизнь! Спасибо. Я потратил часы, пытаясь найти решение этой проблемы, пока не наткнулся на это. (Моя проблема немного отличалась от OP, но решение было таким же.)
Ответ №2:
Как указано в документации, ResponseEntity#ok
возвращает HTTP OK (https://httpstatuses.com/200 ) код состояния. Фильтр запускается раньше, поэтому статус позже перезаписывается.
Вы должны создать ResponseEntity
, используя некоторые другие средства, описанные в https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html .