Reputation: 641
Since after 2 days I still cannot figure how to perform a print of a HttpServletResponse
body in HandlerInterceptorAdapter
, I'll ask another time :)
With HttpServletRequest
I can easily do something like request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
and I have the full body but how to make the same with HttpServletResponse
?
I Had found lots of question on StackOverflow about that but none of them seems to work.
This is the handler:
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
//how to print here the "response" by using the "response" parameter
super.afterCompletion(request, response, handler, ex);
}
this answer is quite the same and links to this but they use ServletResponse
and NOT HttpServletResponse
and something with FilterChain
which I have not in my afterCompletion
handler. Even this that seems the most complete one is not suitable (I think) in my case.
Do someone can provide me a simple serialization example with HttpServletResponse
?
Upvotes: 6
Views: 19927
Reputation: 9085
I ended up with the following in Kotlin:
@Bean
open fun logFilter(): CommonsRequestLoggingFilter {
val filter = InfoRequestLoggingFilter()
filter.setIncludeQueryString(true)
filter.setIncludePayload(true)
filter.setMaxPayloadLength(10000)
filter.isIncludeHeaders = false
return filter
}
import org.apache.commons.io.output.TeeOutputStream
import org.springframework.mock.web.DelegatingServletOutputStream
import org.springframework.web.filter.CommonsRequestLoggingFilter
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.PrintStream
import java.io.PrintWriter
import javax.servlet.FilterChain
import javax.servlet.ServletOutputStream
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import javax.servlet.http.HttpServletResponseWrapper
class InfoRequestLoggingFilter : CommonsRequestLoggingFilter() {
override fun beforeRequest(request: HttpServletRequest, message: String) {
logger.info(message)
}
override fun afterRequest(request: HttpServletRequest, message: String) {
// logger.info(message) - NOP, since doFilterInternal is logging it instead
}
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
val outputStream = ByteArrayOutputStream()
val printStream = PrintStream(outputStream)
filterChain.doFilter(request, object : HttpServletResponseWrapper(response) {
@Throws(IOException::class)
override fun getOutputStream(): ServletOutputStream {
return DelegatingServletOutputStream(TeeOutputStream(super.getOutputStream(), printStream)
)
}
@Throws(IOException::class)
override fun getWriter(): PrintWriter {
return PrintWriter(DelegatingServletOutputStream(TeeOutputStream(super.getOutputStream(), printStream))
)
}
})
logger.info(String.format("%s ;status=%s ;payload=%s", createMessage(request, "", ""), response.status, outputStream.toString()))
}
}
Upvotes: 0
Reputation: 641
It's been hard on searching deeply into it but found that ResponseBodyAdvice
could be suitable for my purposes. So looking for some example on StackOverflow found this guy which had quite same issue having to manipulate the Object body
.
That's my final working solution in order to implement what I wrote here
@ControllerAdvice
public class CSRFHandler implements ResponseBodyAdvice<Object> {
@Value("${security.csrf.enabled}")
private String csrfEnabled;
@Value("${security.csrf.headerName}")
private String csrfHeaderName;
@Value("${security.csrf.salt}")
private String salt;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
if (new Boolean(csrfEnabled).booleanValue()) {
String csrfValue = SecureUtil.buildCsrfValue(salt, StringUtil.toJson(body));
response.getHeaders().add(csrfHeaderName, csrfValue);
}
return body;
}
}
Upvotes: 5
Reputation: 9308
Simple answer is "you can't do that in a Handler Interceptor". Says so in the manual :
HandlerInterceptor is basically similar to a Servlet Filter, but in contrast to the latter it just allows custom pre-processing with the option of prohibiting the execution of the handler itself, and custom post-processing. Filters are more powerful, for example they allow for exchanging the request and response objects that are handed down the chain. Note that a filter gets configured in web.xml, a HandlerInterceptor in the application context.
As a basic guideline, fine-grained handler-related preprocessing tasks are candidates for HandlerInterceptor implementations, especially factored-out common handler code and authorization checks. On the other hand, a Filter is well-suited for request content and view content handling, like multipart forms and GZIP compression. This typically shows when one needs to map the filter to certain content types (e.g. images), or to all requests.
So I advise you check out filter based solutions, as you pointed. You might be interested in : ContentCachingResponseWrapper Produces Empty Response which seems to accomplish what you want with minimal coding. But once you get started with filters, any of the well accepted answers that you have linked to in the question will likely do the job.
Upvotes: 3