使用ContentCachingRequestWrapper后缺少必需的请求正文

问题描述 投票:0回答:3

我需要读取我的其中一个

GenericFilterBean
中的请求负载。 因为我不能两次调用 getreader 我使用了
ContentCachingRequestWrapper
就像-

HttpServletRequest httpRequest = (HttpServletRequest) request;
ContentCachingRequestWrapper cachedRequest = new ContentCachingRequestWrapper(httpRequest);

获取请求体-

String payload = cachedRequest.getReader().lines().collect(Collectors.joining());

并链接请求-

chain.doFilter(cachedRequest, response);

但我的控制器仍然抛出-

 .w.s.m.s.DefaultHandlerExceptionResolver : Resolved exception caused by handler execution: org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public org.springframework.http.ResponseEntity<?> com.siemens.plm.it.sf.api.controllers.OrderController.createCCOrder(com.siemens.plm.it.de.ms.provisioning.model.sap.SapQuoteCreationArgument,javax.servlet.http.HttpServletRequest) throws java.lang.Exception

关于如何链接请求以便我也可以读取控制器中的有效负载有什么想法吗?

谢谢你。

java spring-boot http filtering
3个回答
6
投票

我遇到了同样的问题。您需要在 requestWrapper 上调用 getInputStream 才能让它被缓存。这就是你的过滤器的样子:

@Component
public class CachingRequestBodyFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper((HttpServletRequest) servletRequest);

        //this line is necessary to cache InputStream
        wrappedRequest.getInputStream();
        //String requestBody = IOUtils.toString(wrappedRequest.getInputStream(), StandardCharsets.UTF_8); with this line you can map requestBody to String


        chain.doFilter(wrappedRequest, servletResponse);
    }
}

控制器:

@PostMapping(ENDPOINT)
    void endpoint(HttpServletRequest request) {
        ContentCachingRequestWrapper requestWrapper = (ContentCachingRequestWrapper) request;
        String requestBody = new String(requestWrapper.getContentAsByteArray());
        //process requestBody
}

0
投票

我想通过这样做:

String payload = cachedRequest.getReader().lines().collect(Collectors.joining());

你做错了。

试试这个:

String input = ByteSource.wrap(cachedRequest.getContentAsByteArray())
    .asCharSource(StandardCharsets.UTF_8).read();

正如文档所示:

缓存从输入流和读取器读取的所有内容,并允许通过字节数组检索该内容

一些有用的链接: 链接 1链接 2


0
投票

一般来说,您可以采用 Krzysztof 的方法,即所有网站/教程中显示的方法。

但我想继续直接在 WebResource/RestController 中接收

@RequestBody
。为此,这种方法不起作用,因为 Spring 在内部不会调用
getContentAsByteArray()

所以,这是另一种方法:

对于

@RequestBody
,Spring 使用来自InputStream 的方法
read()
。所以我们需要重写那个。为此,我们必须创建新的包装器。以 Kt 为单位的样本:

class RequestBodyContentWrapper(private val wrapped: ContentCachingRequestWrapper) : ContentCachingRequestWrapper(wrapped) {

    private val bodyProcessorIS: RequestBodyInputStreamWrapper by lazy {
        RequestBodyInputStreamWrapper(super.getInputStream())
    }

    override fun getInputStream(): ServletInputStream {
        return bodyProcessorIS
    }

    // use the method supported by ContentCachingRequestWrapper to read the bytes
    fun prepareInputStream() = bodyProcessorIS.resetReading(wrapped.contentAsByteArray)
}

class RequestBodyInputStreamWrapper(private val wrapped: ServletInputStream) : ServletInputStream() {

    private lateinit var bais: ByteArrayInputStream

    // convert the bytes read from the cache to InputStream 
    // which later can be used in the read()
    fun resetReading(bytes: ByteArray) {
        this.bais = ByteArrayInputStream(bytes)
    }

    override fun read() = bais.read()

    override fun isFinished() = wrapped.isFinished

    override fun isReady(): Boolean = wrapped.isReady

    override fun setReadListener(readListener: ReadListener?) = wrapped.setReadListener(readListener)

}

然后,在过滤器中:

override fun doFilterInternal(originalRequest: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
        val contentRequest = ContentCachingRequestWrapper(originalRequest)

        val hmac = ...
        // ... internal logic

        val readableRequest = RequestBodyContentWrapper(contentRequest)
        readableRequest.prepareInputStream()
        filterChain.doFilter(readableRequest, response)
    }

这是必需的,因为 Spring 内部类不会使用

getContentAsByteArray
,而是使用
inputStream.read()
。令人失望的是 Spring 的 ContentCachingRequestWrapper 无法处理该问题(稍后不允许使用
read()
)。

一开始可能不是那么简单,但对我来说很有效。花一些时间来理解它。
我更喜欢这种方式,以保持 RestController 的清洁。过滤器通常是唯一的(也就是说,这个附加逻辑位于单个位置);与我们经常有的 RestController 相反。

© www.soinside.com 2019 - 2024. All rights reserved.