Skip to content

ContentCachingRequestWrapper doesn't cache multipart data #36668

@pascalgn

Description

@pascalgn

As the title says, ContentCachingRequestWrapper does not handle application/x-www-form-urlencoded requests. Particularly, the methods

public Collection<Part> getParts() throws IOException, ServletException;

public Part getPart(String name) throws IOException, ServletException;

from jakarta.servlet.http.HttpServletRequest are not implemented.

Taking AbstractRequestLoggingFilter (which uses ContentCachingRequestWrapper) as example, in its beforeRequest method, calling something like request.getPart("file").getInputStream().readAllBytes() gives the expected result. It's even possible to call it multiple times, so logging it here would be possible and it could still be consumed by the application later. However, this is true for Tomcat and maybe other containers, because they implement multipart handling via temporary files, but I don't think it's required by the spec.

The problem is, in the afterRequest method, ContentCachingRequestWrapper.getContentAsByteArray() is still available and gives the request body, but request.getPart("file").getInputStream() is not, but instead throws java.nio.file.NoSuchFileException. This is because of org.springframework.web.servlet.DispatcherServlet#cleanupMultipart, which deletes the temporary files before the filterChain.doFilter call in AbstractRequestLoggingFilter returns.

One workaround would be to have something like

class PartWrapper implements Part {
	// implement Part methods:
	// [...] 

	@Override
	public void delete() throws IOException {
		// don't delete
	}

	public void reallyDelete() throws IOException {
		part.delete();
	}
}

and

request = new ContentCachingRequestWrapper(request) {
	@Override
	public Collection<Part> getParts() throws IOException, ServletException {
		return super.getParts().stream().map(p -> (Part) new PartWrapper(p)).toList();
	}

	@Override
	public Part getPart(String name) throws IOException, ServletException {
		return new PartWrapper(super.getPart(name));
	}
};

and remember to call reallyDelete when the filter is done.

Other than that, I don't have a good/simple solution to this. As I think it's not an often needed feature, I think the main outcome of this issue should be to simply document this in ContentCachingRequestWrapper and/or AbstractRequestLoggingFilter.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions