← Назад к вопросам

Какие знаешь способы перехвата HTTP запроса?

1.7 Middle🔥 111 комментариев
#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Способы перехвата HTTP запроса

HTTP Interceptors позволяют перехватывать и модифицировать запросы и ответы на глобальном уровне. Это критично для логирования, аутентификации, обработки ошибок и трансформации данных.

1. Spring Filter (Servlet Filter) — нижний уровень

Самый низкоуровневый способ, работает на уровне Servlet

// Регистрация фильтра
@Component
public class LoggingFilter implements Filter {
    
    private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        long startTime = System.currentTimeMillis();
        logger.info("Request: {} {}", httpRequest.getMethod(), httpRequest.getRequestURI());
        
        try {
            // Продолжить цепочку фильтров
            chain.doFilter(request, response);
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            logger.info("Response: {} Status={}, Duration={}ms", 
                httpRequest.getRequestURI(), 
                httpResponse.getStatus(), 
                duration);
        }
    }
}

// Или явная регистрация
@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<LoggingFilter> loggingFilter() {
        FilterRegistrationBean<LoggingFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new LoggingFilter());
        bean.addUrlPatterns("/api/*");  // Применить только к /api/*
        bean.setOrder(1);               // Порядок выполнения
        return bean;
    }
}

Практический пример: Логирование тела запроса/ответа

@Component
@Order(1)
public class RequestBodyLoggingFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain filterChain)
            throws ServletException, IOException {
        
        // Обертка для повторного чтения тела
        HttpServletRequest wrappedRequest = new ContentCachingRequestWrapper(request);
        HttpServletResponse wrappedResponse = new ContentCachingResponseWrapper(response);
        
        try {
            filterChain.doFilter(wrappedRequest, wrappedResponse);
        } finally {
            // Логирование тела запроса
            byte[] requestBody = ((ContentCachingRequestWrapper) wrappedRequest).getContentAsByteArray();
            logger.info("Request body: {}", new String(requestBody, StandardCharsets.UTF_8));
            
            // Логирование тела ответа
            byte[] responseBody = ((ContentCachingResponseWrapper) wrappedResponse).getContentAsByteArray();
            logger.info("Response body: {}", new String(responseBody, StandardCharsets.UTF_8));
            
            // Важно: записать обратно тело ответа
            wrappedResponse.copyBodyToResponse();
        }
    }
}

2. Spring Interceptor (HandlerInterceptor) — на уровне контроллера

Работает междуDispatcherServlet и Controller

@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        
        String token = request.getHeader("Authorization");
        
        if (token == null || !isValidToken(token)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("Unauthorized");
            return false;  // Остановить обработку
        }
        
        // Сохранить пользователя в контексте
        request.setAttribute("user", getUserFromToken(token));
        return true;  // Продолжить обработку
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, 
                          Object handler, ModelAndView modelAndView) throws Exception {
        // Вызывается после обработки контроллером, но до отправки ответа
        logger.info("Post-processing request");
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                               Object handler, Exception ex) throws Exception {
        // Вызывается после отправки ответа
        if (ex != null) {
            logger.error("Error during request processing", ex);
        }
    }
}

// Регистрация
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
            .addPathPatterns("/api/**")           // Применить к /api/**
            .excludePathPatterns("/api/auth/**"); // Исключить /api/auth/**
    }
}

3. RestTemplate Interceptor (клиентский перехват)

Перехват исходящих HTTP запросов

@Component
public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {
    
    private static final Logger logger = LoggerFactory.getLogger(RestTemplateInterceptor.class);
    
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, 
                                       ClientHttpRequestExecution execution)
            throws IOException {
        
        long startTime = System.currentTimeMillis();
        
        // Добавить заголовки ко всем запросам
        request.getHeaders().add("X-Request-ID", UUID.randomUUID().toString());
        request.getHeaders().add("X-Client-Version", "1.0.0");
        
        logger.info("Outgoing request: {} {}", request.getMethod(), request.getURI());
        
        // Выполнить запрос
        ClientHttpResponse response = execution.execute(request, body);
        
        long duration = System.currentTimeMillis() - startTime;
        logger.info("Response received: Status={}, Duration={}ms", 
            response.getStatusCode(), duration);
        
        return response;
    }
}

// Конфигурация
@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(RestTemplateInterceptor interceptor) {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setInterceptors(Collections.singletonList(interceptor));
        return restTemplate;
    }
}

4. WebClient Interceptor (Reactive, Spring WebFlux)

Для асинхронных запросов

@Configuration
public class WebClientConfig {
    @Bean
    public WebClient webClient() {
        return WebClient.builder()
            .filter(ExchangeFilterFunction.ofRequestProcessor(request -> {
                // Логирование запроса
                logger.info("Request: {} {}", request.getMethod(), request.getURL());
                
                // Добавить заголовки
                request.getHeaders().add("Authorization", getAuthToken());
                request.getHeaders().add("X-Request-ID", UUID.randomUUID().toString());
                
                return Mono.just(request);
            }))
            .filter(ExchangeFilterFunction.ofResponseProcessor(response -> {
                // Логирование ответа
                logger.info("Response status: {}", response.getStatusCode());
                return Mono.just(response);
            }))
            .build();
    }
}

// Использование
@Service
public class ExternalApiService {
    @Autowired
    private WebClient webClient;
    
    public Mono<String> fetchData() {
        return webClient
            .get()
            .uri("https://api.example.com/data")
            .retrieve()
            .bodyToMono(String.class)
            .onErrorResume(error -> {
                logger.error("Error fetching data", error);
                return Mono.empty();
            });
    }
}

5. Aspect-Oriented Programming (AOP) — через аннотации

Использование @Aspect для перехвата методов контроллера

@Aspect
@Component
public class RequestLoggingAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(RequestLoggingAspect.class);
    
    @Pointcut("execution(* com.example.controller..*(..))")
    public void controllerPointcut() {}
    
    @Before("controllerPointcut()")
    public void logBefore(JoinPoint joinPoint) {
        logger.info("Calling method: {}.{}", 
            joinPoint.getTarget().getClass().getSimpleName(),
            joinPoint.getSignature().getName());
        
        Object[] args = joinPoint.getArgs();
        logger.info("Method arguments: {}", Arrays.toString(args));
    }
    
    @After("controllerPointcut()")
    public void logAfter(JoinPoint joinPoint) {
        logger.info("Exiting method: {}.{}", 
            joinPoint.getTarget().getClass().getSimpleName(),
            joinPoint.getSignature().getName());
    }
    
    @Around("controllerPointcut()")
    public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        try {
            Object result = pjp.proceed();  // Вызвать оригинальный метод
            return result;
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            logger.info("Method execution time: {}ms", duration);
        }
    }
}

6. Custom Annotation для перехвата

Создание специализированного перехвата

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogRequest {
    String value() default "";
}

@Aspect
@Component
public class LogRequestAspect {
    
    @Around("@annotation(logRequest)")
    public Object logRequest(ProceedingJoinPoint pjp, LogRequest logRequest) throws Throwable {
        logger.info("Logging request: {}", logRequest.value());
        return pjp.proceed();
    }
}

// Использование
@RestController
public class UserController {
    @GetMapping("/users/{id}")
    @LogRequest("Fetching user by ID")
    public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
        // ...
    }
}

7. Перехват в Spring Security

Специализированный перехват для безопасности

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Bean
    public SecurityContextHolderAwareRequestFilter securityContextHolderAwareRequestFilter() {
        return new SecurityContextHolderAwareRequestFilter();
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
            .antMatchers("/api/auth/**").permitAll()
            .antMatchers("/api/**").authenticated()
            .and()
            .csrf().disable();
    }
}

// Custom фильтр
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        
        String token = request.getHeader("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            token = token.substring(7);
            
            try {
                User user = parseToken(token);
                UserDetails userDetails = new User(user.getUsername(), "", user.getAuthorities());
                Authentication auth = new UsernamePasswordAuthenticationToken(
                    userDetails, null, userDetails.getAuthorities());
                
                SecurityContextHolder.getContext().setAuthentication(auth);
            } catch (Exception e) {
                logger.error("Token validation failed", e);
            }
        }
        
        filterChain.doFilter(request, response);
    }
}

Сравнение способов

┌─────────────────────────────────────────────────────────┐
│ Filter (Servlet)                                        │
│ - Самый низкоуровневый                                  │
│ - Работает ДО Spring                                    │
│ - Идеален для логирования, CORS, сжатия                 │
├─────────────────────────────────────────────────────────┤
│ Interceptor (HandlerInterceptor)                        │
│ - На уровне Spring MVC                                  │
│ - Имеет доступ к Model, Handler, ModelAndView           │
│ - Идеален для аутентификации, авторизации               │
├─────────────────────────────────────────────────────────┤
│ AOP (@Aspect)                                          │
│ - На уровне методов компонентов                         │
│ - Идеален для кроссрезовых задач (логирование, метрики) │
├─────────────────────────────────────────────────────────┤
│ RestTemplate/WebClient Interceptor                      │
│ - Для исходящих запросов                                │
│ - Добавление заголовков, retry logic                    │
└─────────────────────────────────────────────────────────┘

Best Practices

  1. Используй Filter для общей логики (CORS, логирование)
  2. Используй Interceptor для бизнес-логики (аутентификация, авторизация)
  3. Используй AOP для кроссрезовых задач (метрики, профилирование)
  4. Логируй все HTTP запросы для отладки
  5. Добавляй Request ID для отслеживания в распределенной системе
  6. Не обрабатывай в фильтре то, что можно в контроллере — разделение ответственности
  7. Помни об производительности — минимизируй обработку в фильтрах

Правильный выбор способа перехвата критичен для чистой архитектуры приложения.