Primavera - Registrar solicitações de entrada

Primavera - Registrar solicitações de entrada

1. Introdução

Neste tutorial rápido, vamos mostrar os fundamentos do registro de solicitações de entrada usando o filtro de registro do Spring. Se você está apenas começando a registrar em log, verifique estelogging intro article, bem como oSLF4J article.

2. Dependências do Maven

As dependências de registro serão simplesmente as mesmas do artigo de introdução; vamos simplesmente adicionar Spring aqui:


    org.springframework
    spring-core
    5.0.5.RELEASE

A versão mais recente pode ser encontrada aqui paraspring-core.

3. Controlador Web Básico

Primeiro, vamos definir um controlador que será usado em nosso exemplo:

@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. Registro de solicitação personalizada

O Spring fornece um mecanismo para configurar interceptores definidos pelo usuário para executar ações antes e depois de solicitações da Web.

Entre os interceptores de solicitação Spring, uma das interfaces dignas de nota éHandlerInterceptor, que pode ser usada para registrar a solicitação de entrada implementando os seguintes métodos:

  1. preHandle() – este método é executado antes do método de serviço do controlador real

  2. afterCompletion() – este método é executado depois que o controlador está pronto para enviar a resposta

Além disso, o Spring fornece a implementação padrão da interfaceHandlerInterceptor na forma de classeHandlerInterceptorAdaptor que pode ser estendida pelo usuário.

Vamos criar nosso próprio interceptor - estendendoHandlerInterceptorAdaptor as:

@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) {
        //
    }
}

Finalmente, vamos configurar oTaxiRideRequestInterceptor dentro do ciclo de vida MVC para capturar pré e pós-processamento de invocações de método do controlador que são mapeadas para o caminho/taxifare definido na classeTaxiFareController.

@Configuration
public class TaxiFareMVCConfig implements WebMvcConfigurer {

    @Autowired
    private TaxiFareRequestInterceptor taxiFareRequestInterceptor;

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

Em conclusão,WebMvcConfigurer adicionaTaxiFareRequestInterceptor dentro do ciclo de vida MVC da mola invocando o métodoaddInterceptors().

O maior desafio é obter as cópias da carga útil de solicitação e resposta para o log e ainda deixar a carga útil solicitada para o servlet processá-lo.

O principal problema com a solicitação de leitura é que, assim que o fluxo de entrada é lido pela primeira vez, ele é marcado como consumido e não pode ser lido novamente.

O aplicativo lançará uma exceção depois de ler o fluxo de solicitação:

{
  "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/"
}

To overcome this problem, podemos aproveitar o cache para armazenar o fluxo de solicitação e usá-lo para registro.

O Spring fornece algumas classes úteis, comoContentCachingRequestWrappereContentCachingResponseWrapper, que podem ser usadas para armazenar em cache os dados de solicitação para fins de registro.

Vamos ajustar nossopreHandle() da classeTaxiRideRequestInterceptor para armazenar em cache o objeto de solicitação usando a classeContentCachingRequestWrapper.

@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;
}

Como podemos ver, armazenamos em cache o objeto de solicitação usando a classeContentCachingRequestWrapper que pode ser usada para ler os dados de carga útil para registro sem perturbar o objeto de solicitação real:

requestCacheWrapperObject.getContentAsByteArray();

Limitação

  • A classeContentCachingRequestWrapper suporta apenas o seguinte:

Content-Type:application/x-www-form-urlencoded
Method-Type:POST
  • Devemos invocar o seguinte método para garantir que os dados da solicitação sejam armazenados em cache emContentCachingRequestWrapper antes de usá-los:

requestCacheWrapperObject.getParameterMap();

5. Registro de solicitação integrada do Spring

O Spring fornece uma solução integrada para registrar cargas úteis. Podemos usar filtros prontos conectando o aplicativo Spring usando a configuração.

AbstractRequestLoggingFilter é um filtro que fornece funções básicas de registro. As subclasses devem substituir os métodosbeforeRequest()eafterRequest() para realizar o log real em torno da solicitação.

O framework Spring fornece três classes de implementação concretas que podem ser usadas para registrar a solicitação recebida. Essas três classes são:

  • CommonsRequestLoggingFilter

  • Log4jNestedDiagnosticContextFilter (obsoleto)

  • ServletContextRequestLoggingFilter

Agora, vamos passar para oCommonsRequestLoggingFiltere configurá-lo para capturar a solicitação de entrada para registro.

5.1. Configurar aplicativo Spring Boot

O aplicativo Spring Boot pode ser configurado adicionando uma definição de bean para ativar o log de solicitação:

@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;
    }
}

Além disso, esse filtro de log exige que o nível de log seja definido como DEBUG. Podemos habilitar o modo DEBUG adicionando o elemento abaixo emlogback.xml:


    

Outra maneira de habilitar o log do nível DEBUG é adicionar o seguinte emapplication.properties:

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

5.2. Configurar aplicativo da Web tradicional

No aplicativo da web Spring padrão,Filter pode ser definido por meio da configuração XML ou da configuração Java. Vamos configurar oCommonsRequestLoggingFilter usando a configuração convencional baseada em Java.

Como sabemos, o atributoincludePayload deCommonsRequestLoggingFilter é definido como falso por padrão. Precisaríamos de uma classe personalizada para substituir o valor do atributo para habilitarincludePayload antes de injetar no contêiner usando a configuração Java:

public class CustomeRequestLoggingFilter
  extends CommonsRequestLoggingFilter {

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

Agora, precisamos injetarCustomeRequestLoggingFilter usandoJava based web initializer:

public class CustomWebAppInitializer implements
  WebApplicationInitializer {
    public void onStartup(ServletContext container) {

        AnnotationConfigWebApplicationContext context
          = new AnnotationConfigWebApplicationContext();
    context.setConfigLocation("com.example");
    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. Exemplo em ação

Agora, podemos conectar um Spring Boot com contexto e ver em ação que o registro de solicitações recebidas funciona conforme o esperado:

@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. Conclusão

Neste artigo, mostramos como implementar o log de solicitações básicas da Web usando interceptores; também mostramos as limitações e os desafios dessa solução.

Em seguida, mostramos a classe de filtro integrada, que fornece mecanismo de registro simples e pronto para usar.

Como sempre, a implementação do exemplo e trechos de código estão disponíveisover on GitHub.