23

Logic I have to implement is logging all requests with body served to DB.

So I decided to use: afterCompletion method of HandlerInterceptor. There are two parameters passed to this method HttpServletRequest and HttpServletResponse among the others.

Question is: how to get RequestBody and ResponseBody from supplied objects?

As far as I know at Controller we can use @RequestBody and @ResponseBody. Can I reuse them at HandlerInterceptor?

Marcin Zajączkowski
  • 4,036
  • 1
  • 32
  • 41
kyberorg
  • 637
  • 1
  • 8
  • 22

8 Answers8

32

You can extend RequestBodyAdviceAdapter and implement the method afterBodyRead:

@ControllerAdvice
public MyRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter {

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
            Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {

        // write body -your input dto- to logs or database or whatever

        return body;
    }

}

The RequestBodyAdvice comes in pre setp the request chain before the HandlerInterceptor. it is exactly after the http request inputstream is converted to your object.

Muhammad Hewedy
  • 29,102
  • 44
  • 127
  • 219
  • 1
    This was the last resort for me, however just figured out that afterBodyRead is executed after stepping out of my interceptor (as expected) but in my IntegrationTests it's the other way around... – DanDan Jun 26 '19 at 12:02
29

As far as I know, RequestBody and ResponseBody can be read only once. So you should not read them in an Interceptor. Here's some explanation.

Community
  • 1
  • 1
marcellsimon
  • 666
  • 7
  • 14
  • In my case, while doing HTTP POST I want to remove the SOAP tags and want to keep simple XML, how ans where I would need to do this ? – PAA Dec 26 '19 at 08:25
  • 1
    @simonmarcell - No. You can make copy. Just need to think out of the box. – Abhishek Jun 12 '20 at 06:50
  • Yes,for example in postHandle method, notice that the stream is already consumed and you can not access to it at this point.And if execute response.isCommitted() result is true. As other suggestions you would try with Fiter (such as ContentCachingResponseWrapper, CachedBodyHttpServletRequest) using wrappers for the chain executions – danipenaperez May 26 '21 at 09:35
9

As others said, you can not read request input stream or response output stream more than once, but, you can use Filters to replace the original request and response objects with wrapped ones. This way you can implement your wrapper and bufferize the payload, this way you can use it how many times you want.

Here a repo with the working code: https://github.com/glaudiston/spring-boot-rest-payload-logging

Just do a mvn clean install on it and be happy.

$ java -jar target/rest-service-0.0.1-SNAPSHOT.jar                                                                                                                                                                                                                               

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.2.RELEASE)

2020-01-24 13:06:07.297  INFO 918 --- [           main] c.e.restservice.RestServiceApplication   : Starting RestServiceApplication v0.0.1-SNAPSHOT on ca275nt with PID 918 (/home/ggs/src/spring-boot-rest-payload-logging/target/rest-service-0.0.1-SNAPSHOT.jar started by ggs
in /home/ggs/src/spring-boot-rest-payload-logging)
2020-01-24 13:06:07.301  INFO 918 --- [           main] c.e.restservice.RestServiceApplication   : No active profile set, falling back to default profiles: default                                                                                                             
2020-01-24 13:06:08.331  INFO 918 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)                                                                                                                                 
2020-01-24 13:06:08.348  INFO 918 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-01-24 13:06:08.348  INFO 918 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.29]                                                                                                                              
2020-01-24 13:06:08.410  INFO 918 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext                                                                                                                           
2020-01-24 13:06:08.410  INFO 918 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1044 ms                                                                                                              
2020-01-24 13:06:08.627  INFO 918 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'                                                                                                                       
2020-01-24 13:06:08.787  INFO 918 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''                                                                                                                  
2020-01-24 13:06:08.791  INFO 918 --- [           main] c.e.restservice.RestServiceApplication   : Started RestServiceApplication in 1.928 seconds (JVM running for 2.319)                                                                                                      
2020-01-24 13:06:11.014  INFO 918 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'                                                                                                                    
2020-01-24 13:06:11.014  INFO 918 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'                                                                                                                                     
2020-01-24 13:06:11.022  INFO 918 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 8 ms
2020-01-24 13:06:11 [] INFO  RequestFilter:23 - doFilter, parsing request
2020-01-24 13:06:11 [] INFO  LogApiInterceptor:64 - Request Method: POST
2020-01-24 13:06:11 [] INFO  LogApiInterceptor:65 - Request Headers:
2020-01-24 13:06:11 [] INFO  LogApiInterceptor:66 - host:localhost:8080
user-agent:curl/7.64.0
accept:*/*
content-length:32
content-type:application/x-www-form-urlencoded

2020-01-24 13:06:11 [] INFO  LogApiInterceptor:67 - Request body:
2020-01-24 13:06:11 [] INFO  LogApiInterceptor:68 - testdata=123456789&test2=9876543
2020-01-24 13:06:11 [] INFO  LogApiInterceptor:75 - Response Status: 200
2020-01-24 13:06:11 [] INFO  LogApiInterceptor:76 - Response Headers:
2020-01-24 13:06:11 [] INFO  LogApiInterceptor:77 -
2020-01-24 13:06:11 [] INFO  LogApiInterceptor:78 - Response body:
2020-01-24 13:06:11 [] INFO  LogApiInterceptor:84 - {"id":1,"content":"Hello, World!"}
──────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ curl -X POST --data "testdata=123456789&test2=9876543" http://localhost:8080/greeting
{"id":1,"content":"Hello, World!"}
ton
  • 3,827
  • 1
  • 42
  • 40
  • 1
    It would be nice to know why my answers were downvoted. – ton Dec 23 '19 at 12:33
  • `writePayloadAudit` has different signatures when used and when declared, how do you get the request body? – zjor Jan 22 '20 at 11:04
  • Here a working code repo to you, just `mvn clean install`, the tests will show it working. https://github.com/glaudiston/spring-boot-rest-payload-logging – ton Jan 22 '20 at 18:23
  • thanks for your effort but your example doesn't work for POST requests – zjor Jan 23 '20 at 22:01
  • The code actually do work with any request, but the sample endpoint only support GET request. Then the response body is blocked before the log interceptor. – ton Jan 24 '20 at 11:14
  • This actually works, just needs a little updating using HandlerInterceptor in place of the deprecated HandlerInterceptorAdapter. – Santiago Trias Aug 11 '23 at 20:46
3

Here's the relevant statement from javadoc for HandlerInterceptor javadoc.

Callback after completion of request processing, that is, after rendering the view. Will be called on any outcome of handler execution, thus allows for proper resource cleanup.

HandlerIntercepter Javadoc

You cannot access the request body (as an InputStream) because the request was already read. If you need access to request parameters, you could do so using the request object by calling - request.getParameter("parameterName");

You cannot access the response body because the response is already rendered. That means the response is already committed to the outputstream.

Sashi
  • 1,977
  • 16
  • 15
  • This is the javadoc for the `afterCompletion` method. `preHandle` happens before the request is handled. – rewolf Sep 16 '20 at 05:40
3

You can get the request body from HTTPServlet request inside HandlerInterceptorAdapter methods using the method:

request.getInputStream();

If you want it to use with scanner you can assign the scanner as follows:

Scanner scanner = new Scanner(request.getInputStream(), "UTF-8");

Then you can use the required data from the scanner using some manipulation or scanner tricks. Or else you can assign it to a string also using Apache Commons IO:

String requestStr = IOUtils.toString(request.getInputStream());
Aarya
  • 447
  • 5
  • 5
3

This is quite an old thread but for the sake of people who looking for a way to get RequestBody and ResponseBody from Interceptor. Here is how I got it working.

RequestBody, can simply use IOUtils:

String requestBody= IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);

ResponseBody, I had to used HttpServletResponseCopier:

ServletLoggingFilter.HttpServletResponseCopier copier = (ServletLoggingFilter.HttpServletResponseCopier) response;
String responseBody = new String(copier.getCopy());
TorIsHere
  • 119
  • 2
  • 8
  • 2
    To get the body the second parameter should get the name, just like `UTF-8`, and where can we find `ServletLoggingFilter.HttpServletResponseCopier`? – EliuX Jan 20 '17 at 23:48
  • hi, didn't you get the issue related with request is already read. (I cannot remember the exact error message). ? – vigamage Aug 16 '17 at 06:22
  • Yes, you will get the same error "getReader() has already been called for this request" – jzonthemtn Sep 13 '17 at 13:03
  • This does not work for me as I get a request body missing error. – Stewart Feb 06 '19 at 05:50
  • @Stewart, Were you able to resolve the issue of "request body missing error"? I am getting the same error! – Ram Apr 29 '20 at 10:42
3

You may use ResponseBodyAdvice available since Spring 4.1, with which you could intercept the response body before the body is written to the client.

Here is an example: https://sdqali.in/blog/2016/06/08/filtering-responses-in-spring-mvc/

Mingjiang Shi
  • 7,415
  • 2
  • 26
  • 31
0

As request body is available as stream in interceptor is becomes unavailable after read. If you need body in interceptor and later in controller you need to cache it in your own wrapper: https://tomsdevblog.com/read-message-body-in-request-interceptor/

Tom
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Mar 17 '23 at 21:29