关于多次消耗请求主体的问题

huangapple go评论65阅读模式
英文:

Problems about comsuming request body for multiple times

问题

我是一名新的Java开发者。最近,我正在开发一个新的Spring Boot项目,我想在进入MVC控制器之前打印请求体(确切地说,我想打印具有contentType:"application/json"的POST请求的请求体)。

我使用以下的请求包装器(RequestWrapper):

public class MyRequestWrapper extends HttpServletRequestWrapper {
    private byte[] cachedBody = new byte[]{};
    private InputStream input = null;

    public MyRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        if (request.getContentType() != null && (request.getContentType().contains("multipart/") ||
                request.getContentType().contains("/x-www-form-urlencoded"))) {
            cachedBody = new byte[]{};
            input = request.getInputStream();
        } else {
            cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
            input = new ByteArrayInputStream(cachedBody);
        }
    }

    @Override
    public ServletInputStream getInputStream() {
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return input.read();
            }
        };
    }

    public String getBody() {
        return new String(cachedBody);
    }
}

然后,我使用一个过滤器来打印请求内容:

@WebFilter(filterName = "RequestResponseFilter", urlPatterns = "/*", asyncSupported = true)
public class RequestResponseFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        MyRequestWrapper requestWrapper = (MyRequestWrapper) request;
        // ...
        System.out.println(requestWrapper.getBody());
        // ...
        chain.doFilter(requestWrapper, response);
    }
}

以下是我的控制器:

@PostMapping(value="/test")
public ResponseData<String> test(
        @RequestParam("id") String id,
        @RequestParam("value") String value) {
    ResponseData<String> result = new ResponseData<>();
    result.setData(id + value);
    result.setCode(Constants.CODE_SUCCESS);
    return result;
}

然而,当我使用Postman测试我的代码时,它没有正常工作。如果我使用POST方法并传递参数,同时设置内容类型为"application/x-www-form-urlencoded",它会抛出"org.springframework.web.bind.MissingServletRequestParameterException"异常。

让我困惑的是,如果我使用内容类型"multipart/form-data"传递参数,它却能正常工作。

此外,我尝试过使用Spring提供的CachedBodyHttpServletRequest,但直到请求进入控制器之前,它都无法获取请求内容。

为什么MVC控制器无法正常获取带有@RequestParam注解的参数?我该如何修复它?

英文:

I'm a new javaer. Recently I'm working on a new springboot project, and I want to print request body before it enter mvc controller. (To be exact, I want to print request body of post request with contentType:"application/json")

I use a requestWrapper as below.

public class MyRequestWrapper extends HttpServletRequestWrapper {
    private byte[] cachedBody = new byte[]{};
    private InputStream input = null;

    public MyRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        if (request.getContentType() != null &amp;&amp; (request.getContentType().contains(&quot;multipart/&quot;) ||
                request.getContentType().contains(&quot;/x-www-form-urlencoded&quot;))) {
            cachedBody = new byte[]{};
            input = request.getInputStream();
        } else {
            cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
            input = new ByteArrayInputStream(cachedBody);
        }
    }

    @Override
    public ServletInputStream getInputStream() {
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return input.read();
            }
        }
    }

    public String getBody() {
        return new String(cachedBody);
    }

Then, I use a filter to print the request content.

@WebFilter(filterName = &quot;RequestResponseFilter&quot;, urlPatterns = &quot;/*&quot;, asyncSupported = true)
public class RequestResponseFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        MyRequestWrapper requestWrapper = (MyRequestWrapper) request;
        ......
        System.out.println(requestWrapper.getBody());
        ......
        chain.doFilter(requestWrapper, response);
    }
}

Below is my controller.

@PostMapping(value=&quot;/test&quot;)
public ResponseData&lt;String&gt; test(
        @RequestParam(&quot;id&quot;) String id,
        @RequestParam(&quot;value&quot;) String value) {
    ResponseData&lt;String&gt; result = new ResponseData&lt;&gt;();
    result.setData(id + value);
    result.setCode(Constants.CODE_SUCCESS);
    return result;
}

However, when I use postman to test my code, it didn't work well. If I use post method and pass param with content-type:"application/x-www-form-urlencoded", it throws "org.springframework.web.bind.MissingServletRequestParameterException".

What confuse me is that, if I pass param with content-type:"multipart/form-data", it work well.

Besides, I have tried CachedBodyHttpServletRequest which provided by spring. But it couldn't get request content until the request enter controller.

Why the mvc controller failed to get param with annotation @RequestParam? And how can I fix it?

答案1

得分: 1

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// param
request.getParameterMap().forEach((k, v) -> System.out.println(k + " : " + v[0]));
// body
byte[] array = StreamUtils.copyToByteArray(request.getInputStream());
System.out.println(new String(array, StandardCharsets.UTF_8));
}

但如果您使用multipart/form-data上传文件,则文件内容无法转换为字符串,您需要工具来解决这个问题,类似于以下内容:

if (contentType.startsWith("multipart/form-data")) {
StandardServletMultipartResolver resolver = new StandardServletMultipartResolver();
StandardMultipartHttpServletRequest req = (StandardMultipartHttpServletRequest) resolver.resolveMultipart((HttpServletRequest) request);
System.out.println(req.getMultiFileMap());
System.println(req.getParameterMap());
}

英文:

u can get param & body like this

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    // param
    request.getParameterMap().forEach((k, v) -&gt; System.out.println(k + &quot; : &quot; + v[0]));
    // body
    byte[] array = StreamUtils.copyToByteArray(request.getInputStream());
    System.out.println(new String(array, StandardCharsets.UTF_8));
}

but if u upload file with multipart/form-data then file content can't cast to String, u need tools to resolve it, something like this

if (contentType.startsWith(&quot;multipart/form-data&quot;)) {
    StandardServletMultipartResolver resolver = new StandardServletMultipartResolver();
    StandardMultipartHttpServletRequest req = (StandardMultipartHttpServletRequest)resolver.resolveMultipart((HttpServletRequest) request);
    System.out.println(req.getMultiFileMap());
    System.out.println(req.getParameterMap());
}

答案2

得分: 0

你可以在这里阅读文章:链接

但对于multipart/form-data,将类更改为扩展以下内容:

public class CachedBodyHttpServletFormRequest extends StandardMultipartHttpServletRequest {}
英文:

You can read article here: Link

But with multipart/form-data, change class to extend below:
public class CachedBodyHttpServletFormRequest extends StandardMultipartHttpServletRequest {}

huangapple
  • 本文由 发表于 2023年2月16日 12:56:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/75468005.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定