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:
-
preHandle() – este método é executado antes do método de serviço do controlador real
-
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.