Рубрики
Без рубрики

Весенний Журнал Входящих Запросов

Узнайте, как регистрировать данные HTTP-запросов с помощью Spring.

Автор оригинала: baeldung.

1. введение

В этом кратком руководстве мы покажем основы регистрации входящих запросов с помощью фильтра регистрации Spring. Если вы только начинаете вести журнал, ознакомьтесь с этой статьей , посвященной ведению журнала, а также со статьей SLF4J .

2. Зависимости Maven

Зависимости ведения журнала будут просто такими же, как и во вступительной статье; давайте просто добавим сюда Spring:


    org.springframework
    spring-core
    5.2.2.RELEASE   

Последнюю версию можно найти здесь для spring-core .

3. Базовый веб-Контроллер

Прежде всего, давайте определим контроллер, который будет использоваться в нашем примере:

@RestController
public class TaxiFareController {

    @GetMapping("/taxifare/get/")
    public RateCard getTaxiFare() {
        return new RateCard();
    }
    
    @PostMapping("/taxifare/calculate/")
    public String calculateTaxiFare(
      @RequestBody @Valid TaxiRide taxiRide) {
 
        // return the calculated fare
    }
}

4. Ведение журнала пользовательских Запросов

Spring предоставляет механизм для настройки пользовательских перехватчиков для выполнения действий до и после веб-запросов.

Среди перехватчиков запросов Spring одним из примечательных интерфейсов является HandlerInterceptor , который можно использовать для регистрации входящего запроса , реализуя следующие методы:

  1. preHandle() – этот метод выполняется перед фактическим методом обслуживания контроллера
  2. afterCompletion() – этот метод выполняется после того, как контроллер готов отправить ответ

Кроме того, Spring предоставляет реализацию по умолчанию интерфейса HandlerInterceptor в виде класса HandlerInterceptorAdaptor , который может быть расширен пользователем.

Давайте создадим наш собственный перехватчик – путем расширения HandlerInterceptorAdaptor как:

@Component
public class TaxiFareRequestInterceptor 
  extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(
      HttpServletRequest request, 
      HttpServletResponse response, 
      Object handler) {
        return true;
    }

    @Override
    public void afterCompletion(
      HttpServletRequest request, 
      HttpServletResponse response, 
      Object handler, 
      Exception ex) {
        //
    }
}

Наконец, мы настроим Taxi Ride RequestInterceptor внутри жизненного цикла MVC для захвата предварительной и последующей обработки вызовов методов контроллера, которые сопоставляются с path /taxifare , определенным в TaxiFareController класс.

@Configuration
public class TaxiFareMVCConfig implements WebMvcConfigurer {

    @Autowired
    private TaxiFareRequestInterceptor taxiFareRequestInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(taxiFareRequestInterceptor)
          .addPathPatterns("/**/taxifare/**/");
    }
}

В заключение, WebMvcConfigurer добавляет Taxi Fare RequestInterceptor внутри жизненного цикла spring MVC, вызывая метод addInterceptors () .

Самая большая проблема заключается в том, чтобы получить копии полезной нагрузки запроса и ответа для регистрации и при этом оставить запрошенную полезную нагрузку для сервлета для ее обработки.

Основная проблема с запросом на чтение заключается в том, что, как только входной поток считывается в первый раз, он помечается как потребленный и не может быть прочитан снова.

Приложение выдаст исключение после чтения потока запросов:

{
  "timestamp": 1500645243383,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.http.converter
    .HttpMessageNotReadableException",
  "message": "Could not read document: Stream closed; 
    nested exception is java.io.IOException: Stream closed",
  "path": "/rest-log/taxifare/calculate/"
}

Чтобы преодолеть эту проблему , мы можем использовать кэширование для хранения потока запросов и использовать его для ведения журнала.

Spring предоставляет несколько полезных классов, таких как Content Caching Request Wrapper и Content CachingResponseWrapper , которые можно использовать для кэширования данных запроса в целях ведения журнала.

Давайте настроим наш класс preHandle() of Taxi Ride RequestInterceptor для кэширования объекта запроса с помощью класса ContentCachingRequestWrapper|/.

@Override
public boolean preHandle(HttpServletRequest request, 
  HttpServletResponse response, Object handler) {
 
    HttpServletRequest requestCacheWrapperObject
      = new ContentCachingRequestWrapper(request);
    requestCacheWrapperObject.getParameterMap();
    // Read inputStream from requestCacheWrapperObject and log it
    return true;
}

Как мы можем видеть, мы кэшировали объект запроса, используя Оболочку запроса кэширования содержимого класс, который можно использовать для чтения данных полезной нагрузки для ведения журнала, не нарушая фактический объект запроса:

requestCacheWrapperObject.getContentAsByteArray();

Ограничение

  • Оболочка запроса кэширования содержимого class поддерживает только следующее:
Content-Type:application/x-www-form-urlencoded
Method-Type:POST
  • Мы должны вызвать следующий метод, чтобы убедиться, что данные запроса кэшируются в Оболочке запроса кэширования содержимого перед его использованием:
requestCacheWrapperObject.getParameterMap();

5. Весеннее Встроенное Ведение Журнала Запросов

Spring предоставляет встроенное решение для регистрации полезных нагрузок. Мы можем использовать готовые фильтры, подключившись к приложению Spring с помощью конфигурации.

AbstractRequestLoggingFilter – это фильтр, который обеспечивает основные функции ведения журнала. Подклассы должны переопределять методы before Request() и after Request() для выполнения фактического ведения журнала вокруг запроса.

Spring framework предоставляет три конкретных класса реализации, которые можно использовать для регистрации входящего запроса. Эти три класса являются:

  • CommonsRequestLoggingFilter
  • Log4j Вложенный диагностический контекстный фильтр (устарел)
  • Фильтр регистрации запросов ServletContext

Теперь давайте перейдем к CommonsRequestLoggingFilter и настроим его для захвата входящего запроса для ведения журнала.

5.1. Настройка приложения Spring Boot

Приложение Spring Boot можно настроить, добавив определение компонента, чтобы включить ведение журнала запросов:

@Configuration
public class RequestLoggingFilterConfig {

    @Bean
    public CommonsRequestLoggingFilter logFilter() {
        CommonsRequestLoggingFilter filter
          = new CommonsRequestLoggingFilter();
        filter.setIncludeQueryString(true);
        filter.setIncludePayload(true);
        filter.setMaxPayloadLength(10000);
        filter.setIncludeHeaders(false);
        filter.setAfterMessagePrefix("REQUEST DATA : ");
        return filter;
    }
}

Кроме того, этот фильтр ведения журнала требует, чтобы уровень журнала был настроен на ОТЛАДКУ. Мы можем включить режим ОТЛАДКИ, добавив элемент ниже в logback.xml :


    

Другой способ включить журнал уровня отладки-добавить следующее в application.properties :

logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=
  DEBUG

5.2. Настройка Традиционного Веб-Приложения

В стандартном веб-приложении Spring Filter можно задать с помощью конфигурации XML или конфигурации Java. Давайте настроим CommonsRequestLoggingFilter , используя обычную конфигурацию на основе Java.

Как мы знаем, атрибут include полезной нагрузки фильтра CommonsRequestLoggingFilter по умолчанию имеет значение false. Нам понадобится пользовательский класс, чтобы переопределить значение атрибута, чтобы включить включить полезную нагрузку перед введением в контейнер с помощью конфигурации Java:

public class CustomeRequestLoggingFilter 
  extends CommonsRequestLoggingFilter {

    public CustomeRequestLoggingFilter() {
        super.setIncludeQueryString(true);
        super.setIncludePayload(true);
        super.setMaxPayloadLength(10000);
    }
}

Теперь нам нужно ввести Customer RequestLoggingFilter с помощью веб-инициализатора на основе Java :

public class CustomWebAppInitializer implements 
  WebApplicationInitializer {
    public void onStartup(ServletContext container) {
        
        AnnotationConfigWebApplicationContext context 
          = new AnnotationConfigWebApplicationContext();
	context.setConfigLocation("com.baeldung");
	container.addListener(new ContextLoaderListener(context));
        
	ServletRegistration.Dynamic dispatcher 
          = container.addServlet("dispatcher", 
          new DispatcherServlet(context));
	dispatcher.setLoadOnStartup(1);
	dispatcher.addMapping("/");	
		
	container.addFilter("customRequestLoggingFilter", 
          CustomeRequestLoggingFilter.class)
          .addMappingForServletNames(null, false, "dispatcher");
    }
}

6. Пример в действии

Теперь мы можем подключить пружинную загрузку с контекстом и увидеть в действии, что ведение журнала входящих запросов работает должным образом:

@Test
public void givenRequest_whenFetchTaxiFareRateCard_thanOK() {
    TestRestTemplate testRestTemplate = new TestRestTemplate();
    TaxiRide taxiRide = new TaxiRide(true, 10l);
    String fare = testRestTemplate.postForObject(
      URL + "calculate/", 
      taxiRide, String.class);
 
    assertThat(fare, equalTo("200"));
}

7. Заключение

В этой статье мы показали, как реализовать базовое ведение журнала веб-запросов с помощью перехватчиков; мы также показали ограничения и проблемы этого решения.

Затем мы показали встроенный класс фильтров, который обеспечивает готовый к использованию и простой механизм регистрации.

Как всегда, реализация примера и фрагменты кода доступны на GitHub.