Primavera - Accedi alle richieste in arrivo

1. Introduzione

In questo breve tutorial, mostreremo le basi della registrazione delle richieste in arrivo utilizzando il filtro di registrazione di Spring. Se stai appena iniziando con la registrazione, dai un'occhiata a questo articolo introduttivo alla registrazione, così come l'articolo SLF4J.

2. Dipendenze di Maven

Le dipendenze di registrazione saranno semplicemente le stesse di quelle nell'articolo introduttivo; aggiungiamo semplicemente la primavera qui:

 org.springframework spring-core 5.2.2.RELEASE 

L'ultima versione può essere trovata qui per spring-core.

3. Controller web di base

Prima di tutto, definiamo un controller che verrà utilizzato nel nostro esempio:

@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. Registrazione richiesta personalizzata

Spring fornisce un meccanismo per configurare gli intercettori definiti dall'utente per eseguire azioni prima e dopo le richieste web.

Tra gli Spring request interceptor, una delle interfacce degne di nota è HandlerInterceptor , che può essere utilizzato per registrare la richiesta in arrivo implementando i seguenti metodi:

  1. preHandle (): questo metodo viene eseguito prima del metodo di servizio effettivo del controller
  2. afterCompletion (): questo metodo viene eseguito dopo che il controller è pronto per inviare la risposta

Inoltre, Spring fornisce l'implementazione predefinita dell'interfaccia HandlerInterceptor sotto forma di classe HandlerInterceptorAdaptor che può essere estesa dall'utente.

Creiamo il nostro intercettore - estendendo HandlerInterceptorAdaptor come:

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

Infine, configureremo TaxiRideRequestInterceptor all'interno del ciclo di vita MVC per acquisire la pre e la post-elaborazione delle chiamate al metodo del controller che vengono mappate al percorso / taxifare definito nella classe TaxiFareController .

@Configuration public class TaxiFareMVCConfig implements WebMvcConfigurer { @Autowired private TaxiFareRequestInterceptor taxiFareRequestInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(taxiFareRequestInterceptor) .addPathPatterns("/**/taxifare/**/"); } }

In conclusione, WebMvcConfigurer aggiunge TaxiFareRequestInterceptor all'interno del ciclo di vita Spring MVC richiamando il metodo addInterceptors () .

La sfida più grande è ottenere le copie del payload di richiesta e risposta per la registrazione e lasciare comunque il payload richiesto affinché il servlet lo elabori.

Il problema principale con la richiesta di lettura è che, non appena il flusso di input viene letto per la prima volta, viene contrassegnato come consumato e non può essere letto di nuovo.

L'applicazione genererà un'eccezione dopo aver letto il flusso della richiesta:

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

Per superare questo problema , possiamo sfruttare la memorizzazione nella cache per memorizzare il flusso di richieste e utilizzarlo per la registrazione.

Spring fornisce alcune classi utili come ContentCachingRequestWrapper e ContentCachingResponseWrapper che possono essere utilizzate per memorizzare nella cache i dati della richiesta a scopo di registrazione.

Regoliamo la nostra classe preHandle () di TaxiRideRequestInterceptor per memorizzare nella cache l'oggetto richiesta utilizzando la classe 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; }

Come possiamo vedere, abbiamo memorizzato nella cache l'oggetto richiesta utilizzando la classe ContentCachingRequestWrapper che può essere utilizzata per leggere i dati del payload per la registrazione senza disturbare l'effettivo oggetto richiesta:

requestCacheWrapperObject.getContentAsByteArray();

Limitazione

  • La classe ContentCachingRequestWrapper supporta solo quanto segue:
Content-Type:application/x-www-form-urlencoded Method-Type:POST
  • Dobbiamo richiamare il seguente metodo per assicurarci che i dati della richiesta siano memorizzati nella cache in ContentCachingRequestWrapper prima di utilizzarlo:
requestCacheWrapperObject.getParameterMap();

5. Registrazione richiesta incorporata a molla

Spring fornisce una soluzione integrata per registrare i payload. Possiamo usare filtri già pronti collegandoci all'applicazione Spring usando la configurazione.

AbstractRequestLoggingFilter è un filtro che fornisce le funzioni di base della registrazione. Le sottoclassi dovrebbero sovrascrivere i metodi beforeRequest () e afterRequest () per eseguire la registrazione effettiva della richiesta.

Il framework Spring fornisce tre classi di implementazione concrete che possono essere utilizzate per registrare la richiesta in arrivo. Queste tre classi sono:

  • CommonsRequestLoggingFilter
  • Log4jNestedDiagnosticContextFilter (obsoleto)
  • ServletContextRequestLoggingFilter

Ora, passiamo a CommonsRequestLoggingFilter e configuriamolo per acquisire la richiesta in arrivo per la registrazione.

5.1. Configurare l'applicazione Spring Boot

L'applicazione Spring Boot può essere configurata aggiungendo una definizione di bean per abilitare la registrazione delle richieste:

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

Inoltre, questo filtro di registrazione richiede che il livello di registrazione sia impostato su DEBUG. Possiamo abilitare la modalità DEBUG aggiungendo l'elemento sottostante in logback.xml :

Un altro modo per abilitare il registro del livello DEBUG è aggiungere quanto segue in application.properties :

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

5.2. Configurare l'applicazione Web tradizionale

In the standard Spring web application, Filter can be set via either XML configuration or Java configuration. Let's set up the CommonsRequestLoggingFilter using conventional Java based configuration.

As we know, the includePayload attribute of CommonsRequestLoggingFilter is set to false by default. We would need a custom class to override the value of the attribute to enable includePayload before injecting into the container using Java configuration:

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

Now, we need to inject the CustomeRequestLoggingFilter using Java based web initializer:

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. Example in Action

Now, we can wire up a Spring Boot with context and see in action that logging of incoming requests works as expected:

@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. Conclusion

In questo articolo, abbiamo mostrato come implementare la registrazione delle richieste web di base utilizzando gli intercettatori; abbiamo anche mostrato i limiti e le sfide di questa soluzione.

Quindi abbiamo mostrato la classe di filtro incorporata che fornisce un meccanismo di registrazione semplice e pronto per l'uso.

Come sempre, l'implementazione dell'esempio e gli snippet di codice sono disponibili su GitHub.