Affects: 5.3.7
Java Doc in org.springframework.web.util.ContentCachingRequestWrapper
says that "wrapper that caches all content" wihtout any information that only request with POST
method and content type application/x-www-form-urlencoded
are cached (isFormPost
method is responsible for that).
IMHO it is misleading that you have to see class internals to see for what requests content is cached.
Am I right or I am missing something? :)
PS. I can provide PR with docs update if you agree with me.
Comment From: bclozel
I'm not sure I understand your point here.
This isFormPost
method is really about form parameters if indeed the request is a www-form encoded, but does not restrict caching to only this type of requests.
Using the following sample application:
package com.example.contentcaching;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.filter.AbstractRequestLoggingFilter;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.RouterFunctions;
import org.springframework.web.servlet.function.ServerResponse;
@SpringBootApplication
public class ContentCachingApplication {
Logger logger = LoggerFactory.getLogger(ContentCachingApplication.class);
public static void main(String[] args) {
SpringApplication.run(ContentCachingApplication.class, args);
}
@Bean
MyRequestLoggingFilter customLoggingFilter() {
MyRequestLoggingFilter loggingFilter = new MyRequestLoggingFilter();
loggingFilter.setIncludePayload(true);
return loggingFilter;
}
@Bean
RouterFunction<ServerResponse> routerFunction() {
return RouterFunctions.route().POST(request -> ServerResponse.ok().body(request.body(String.class))).build();
}
public static class MyRequestLoggingFilter extends AbstractRequestLoggingFilter {
@Override
protected void beforeRequest(HttpServletRequest request, String message) {
logger.info("BEFORE: " + message);
}
@Override
protected void afterRequest(HttpServletRequest request, String message) {
logger.info("AFTER: " + message);
}
}
}
And the following commands on the CLI:
http -v POST localhost:8080/resource name=Spring
POST /resource HTTP/1.1
Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 18
Content-Type: application/json
Host: localhost:8080
User-Agent: HTTPie/2.4.0
{
"name": "Spring"
}
HTTP/1.1 200
Connection: keep-alive
Content-Length: 18
Content-Type: text/plain;charset=UTF-8
Date: Wed, 16 Jun 2021 09:40:10 GMT
Keep-Alive: timeout=60
{
"name": "Spring"
}
I'm getting the following logs, which show that the request body can be re-read after the request has been processed, so this means the content has been cached::
INFO 23226 --- [nio-8080-exec-1] achingApplication$MyRequestLoggingFilter : BEFORE: Before request [POST /resource]
INFO 23226 --- [nio-8080-exec-1] achingApplication$MyRequestLoggingFilter : AFTER: After request [POST /resource, payload={"name": "Spring"}]
Could you elaborate a bit? Do you have a sample application that illustrates your point? Thanks
Comment From: ziebamarcin
@bclozel thank you for your quick response!
I think that I was too fast with submitting this issue. You are totally right with isFormPost
method - it has nothing to do with decision if request is cached or not.
As a context I need to apply specific logic to some of requests in my application. I have to decide if logic should be applied based on request headers and request body (to be precise GET, POST, PUT, PATCH, DELETE methods are in use).
To avoid java.lang.IllegalStateException: getInputStream() has already been called for this request
I've decided to use ContentCachingRequestWrapper
and apply it in OncePerRequestFilter
.
Example code which does not work - IllegalStateException
is thrown :
public static class MyFilterWithLogic extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String content = request.getReader().lines().collect(Collectors.joining());
//some logic with content
filterChain.doFilter(request, response);
}
}
Example code which does not work - content
is empty string :
public static class MyFilterWithLogic extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
String content = new String(wrappedRequest.getContentAsByteArray());
//some logic with content
filterChain.doFilter(wrappedRequest, response);
}
}
Example code which does work - content
contains request body and controllers works correctly :
public static class MyFilterWithLogic extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
String content = wrappedRequest.getReader().lines().collect(Collectors.joining());
//some logic with content
filterChain.doFilter(wrappedRequest, response);
}
}
After reading documentation carefully I know that ContentCachingRequestWrapper
is "wrapper that caches all content read from the input stream and reader, and allows this content to be retrieved via a byte array." so I need to read request first to have it cached.
But, for me, method ContentCachingRequestWrapper.getContentAsByteArray()
is little bit confusing because:
* method name suggest that I can access request content without any other actions
* from method name I do not know that it returns cached content and if request hasn't been read byte array will be empty
* From method java doc I can read that it returns cached content so I have to go to class java docs and see when contents are cached
I think that I can be one of the few who interpret this docs and implementation like this but maybe it is will be better to improve method name and/or java docs.
What do you think? 😄
Comment From: rstoyanchev
It wouldn't hurt to improve that a bit.
Comment From: andrei-ivanov
Maybe a getContentAsByteArray(boolean read)
should be added, that would read the request.