【问题标题】:@RequestParam with content type application/x-www-form-urlencoded not working in Spring Boot 2.2内容类型为 application/x-www-form-urlencoded 的 @RequestParam 在 Spring Boot 2.2 中不起作用
【发布时间】:2020-05-19 08:31:18
【问题描述】:

从 Spring Boot 2.1.10 迁移到 2.2.4 后,以下方法开始为 params 参数返回 null。这不是 Spring 中的错误,因为它在我制作一个小型示例项目时起作用。 它也适用于没有 Content-Type: application/x-www-form-urlencoded 的普通 GET 和 POST。

@PostMapping(path = "/test", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public void test(@RequestParam Map<String, String> params) {
    System.out.println(params);
}

我发出下面的请求,该请求在一个项目中有效,但在另一个项目中无效。我尝试禁用所有过滤器和参数解析器,但没有任何效果。

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "param1=1&param2=2" http://localhost:8080/test

任何帮助或想法将不胜感激。此外,如果有人可以指出 Spring 解决参数的地方,我可以尝试调试并查看会发生什么。

【问题讨论】:

  • POST 和请求参数?
  • 没看到...也许你应该寻找servlet api的实现...但这似乎不是一个错误。

标签: spring spring-boot content-type


【解决方案1】:

经过无数小时的调试,事实证明这是日志过滤器的问题。我正在阅读这样的请求:

private static final class BufferedRequestWrapper extends HttpServletRequestWrapper {

    private final byte[] buffer;

    BufferedRequestWrapper(HttpServletRequest req) throws IOException {
        super(req);

        // Read InputStream and store its content in a buffer.
        InputStream is = req.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buf = new byte[1024];
        int read;
        while ((read = is.read(buf)) > 0) {
            baos.write(buf, 0, read);
        }
        this.buffer = baos.toByteArray();
    }

    @Override
    public ServletInputStream getInputStream() {
        return new BufferedServletInputStream(new ByteArrayInputStream(this.buffer));
    }

    String getRequestBody() throws IOException {
        return IOUtils.readLines(this.getInputStream(), StandardCharsets.UTF_8).stream()
                .map(String::trim)
                .collect(Collectors.joining());
    }
}

这消耗了来自内容类型application/x-www-form-urlencoded 的查询参数。我的解决方案是排除读取application/x-www-form-urlencoded 的输入流。

if (req.getContentType() == null || (req.getContentType() != null && !req.getContentType().startsWith("application/x-www-form-urlencoded"))) {
    // Read InputStream and store its content in a buffer.
    InputStream is = req.getInputStream();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] buf = new byte[1024];
    int read;
    while ((read = is.read(buf)) > 0) {
        baos.write(buf, 0, read);
    }
    this.buffer = baos.toByteArray();
} else {
    buffer = new byte[0];
}

【讨论】:

  • 感谢您发布此内容,它为我指明了正确的方向,尽管我以不同的方式解决了它。
【解决方案2】:

添加另一个答案,因为我遇到了同样的问题,但以不同的方式解决了它。

我也有一个请求日志过滤器,它以类似于 Jonas Pedersen 的回答中描述的方式包装传入请求并缓存响应输入流。 Spring Boot 从 2.1.2.RELEASE 更新到 2.3.4.RELEASE

我将传入请求包装在缓存输入流的缓存请求包装器中。 对我来说,问题是由于某些(目前未知)原因,request.getParameterValue(String key) 方法返回 null,即使包装的请求显然具有非空参数映射。 只需访问包装的请求参数映射就解决了我的问题......非常奇怪。

原始包装类,使用 Spring Boot 2.1.2.RELEASE:

public class CachingRequestWrapper extends HttpServletRequestWrapper {

    private final byte[] cachedBody;

    public CachingRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        InputStream requestInputStream = request.getInputStream();
        this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new CachedInputStream(this.cachedBody);
    }

    @Override
    public BufferedReader getReader() throws IOException {
        ByteArrayInputStream byteArrayInputStream =
                new ByteArrayInputStream(this.cachedBody);
        String encoding = StringUtils.isEmpty(this.getCharacterEncoding())
                ? StandardCharsets.UTF_8.name()
                : this.getCharacterEncoding();
        return new BufferedReader(new InputStreamReader(byteArrayInputStream, encoding));
    }
}

为了简洁,CachedInputStream 类的实现被省略了。

简单地访问打包的请求映射似乎解决了整个问题。对于 Spring Boot 2.3.4.RELEASE,此版本有效(我删除了一些细节):

public class CachingRequestWrapper extends HttpServletRequestWrapper {

    private final byte[] cachedBody;
    private final Map<String, String[]> parameterMap;

    public CachingRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        parameterMap = request.getParameterMap(); // <-- This was the crucial part
        InputStream requestInputStream = request.getInputStream();
        this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return this.parameterMap; // this was added just to satisfy spotbugs
    }
}

我没有费心去深入研究这个问题,所以我不能说两个 Spring Boot 版本之间发生了什么变化,或者在包装器构造函数中访问参数映射解决了什么问题。

【讨论】:

    猜你喜欢
    • 2020-04-13
    • 2016-04-19
    • 2021-01-01
    • 1970-01-01
    • 2015-04-02
    • 1970-01-01
    • 2021-02-03
    • 2020-04-21
    • 2019-05-04
    相关资源
    最近更新 更多