无法正确读取拦截器中的请求主体 - Spring BOOT 2.0.4

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

我在拦截器中读取请求正文时遇到问题。

getReader()
getInputStream()
都会造成问题。 我的拦截器:

public class MyInterceptor extends HandlerInterceptorAdapter {
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception)
        throws Exception {
    // TODO Auto-generated method stub

}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
        throws Exception {
    // TODO Auto-generated method stub
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    String requestBody = httpRequest.getReader().lines().collect(Collectors.joining(System.lineSeparator()));

//or
// String requestBody = new BufferedReader(new InputStreamReader(httpRequest.getInputStream()))
//                .lines().collect(Collectors.joining("\n"));
//some logic...
    return true;
}

两种方法都失败了,因为 spring 可能在内部某个地方使用了此类资源。 第一个原因java.lang.IllegalStateException:已经为此请求调用了getReader()并且其他缺少所需的请求正文:org.springframework.http.ResponseEntity...

我尝试了一些包装器的解决方法,但没有效果。我认为这是因为我无法像过滤器一样传递包装器(我不想使用过滤器,因为我有常见的异常管理器(@ControllerAdvice)。

这是一个已知问题吗?有什么解决办法吗?

java spring rest spring-boot interceptor
2个回答
6
投票

最后我想通了,所以我将在这里为其他人留下一些简单但有用的建议。 我使用了请求包装器,但为了使其正常工作,我添加了一个具有最高顺序的过滤器,以在执行拦截器之前将每个请求包装到包装器中。现在效果很好;) 这是最重要的代码 - 过滤器将每个请求包装到多读取包装器中(拦截器看起来几乎与上面相同,包装器不是我发明的,在堆栈上找到的,我发现它是最清晰和可读的):

import lombok.SneakyThrows;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class GlobalWrapFilter implements Filter {


    @Override
    @SneakyThrows
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        MultiReadRequest wrapper = new MultiReadRequest((HttpServletRequest) request);
        chain.doFilter(wrapper, response);
    }

    @Override
    public void destroy() {
    }

    class MultiReadRequest extends HttpServletRequestWrapper {

        private String requestBody;

        @SneakyThrows
        public MultiReadRequest(HttpServletRequest request) {
            super(request);
            requestBody = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody.getBytes());
            return new ServletInputStream() {
                @Override
                public boolean isFinished() {
                    return byteArrayInputStream.available() == 0;
                }

                @Override
                public boolean isReady() {
                    return true;
                }

                @Override
                public void setReadListener(ReadListener readListener) {

                }

                @Override
                public int read() throws IOException {
                    return byteArrayInputStream.read();
                }
            };
        }

        @Override
        @SneakyThrows
        public BufferedReader getReader() {
            return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8));
        }
    }
}

0
投票

我没有足够的业力来评论,但@Rembrandt的答案有一个严重的缺陷:

requestBody = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));

此行正在破坏 Spring Boot 应用程序中的每个 utf8 编码。

解决方法是不使用 getReader(),而是使用 .getInputStream() 来获取实际的原始数据而不进行任何解释

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