Как установить код состояния ответа в фильтре, когда контроллер возвращает ResponseEntity?

#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 .