工作中Filter获取了一次HttpServletRequest做其他处理,导致业务流程的@RequestBody无法获取数据

大家好,我最近在工作中,我遇到了一个关于HTTP请求的问题,我想在这里和大家分享一下。

问题是这样的:在处理HTTP请求时,我需要在拦截器中获取请求体,用于提取其中的一个token进行登录校验。然而,当我在控制器层再次尝试获取请求体时,我发现我无法获取到数据。

这个问题的原因是HTTP请求的输入流只能被读取一次。在Java Servlet API中,我们通过ServletRequestgetInputStream()getReader()方法获取请求体。这两个方法返回的都是一次性的资源,也就是说,一旦我们读取了请求体,就无法再次读取它。这是因为这两个方法返回的输入流或读取器是从HTTP请求的输入流创建的,而HTTP请求的输入流是一个只能向前移动的流,不支持回退或重置。这就是为什么我们在拦截器中读取了请求体后,就无法在控制器层再次读取请求体的原因。

为了解决这个问题,我使用了HttpServletRequestWrapper来重置输入流。HttpServletRequestWrapper是ServletRequest的一个子类,它提供了一种方法来修改请求的属性。我创建了一个HttpServletRequestWrapper的子类,这个子类允许我在读取请求体后重置输入流。这是通过在子类的构造函数中读取请求体并将其存储在一个字节数组中实现的。然后,我重写了getInputStream()和getReader()方法,使它们返回一个新的输入流或读取器,这些输入流或读取器是从存储请求体的字节数组中创建的。这样,我就可以在后续的处理中多次读取请求体。

以下是我用于解决这个问题的代码:

public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;

    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        body = HttpHelper.getBodyString(request).getBytes(StandardCharsets.UTF_8);
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            public boolean isFinished() {
                return false;
            }

            public boolean isReady() {
                return false;
            }

            public void setReadListener(ReadListener readListener) {}

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

    public byte[] getBody() {
        return body;
    }
}

public class HttpHelper {
    public static String getBodyString(ServletRequest request) {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = request.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }
}

在拦截器中,我这样使用BodyReaderHttpServletRequestWrapper

public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
        JSONObject requestParamterMap = HttpHelper.getRequestParamterMap((HttpServletRequest) requestWrapper);
        //在拦截器中获取了做了一些业务处理,并将相关值放入Attribute
        if(!CollectionUtils.isEmpty(requestParamterMap)){
           requestWrapper.setAttribute(AppTokenEnum.SOMETHIN.key, requestParamterMap);
        }
        
            // 验证登陆
            if (loginManager.isLogin()) {
                chain.doFilter(requestWrapper, response);
            } else {
                // 跳转到登陆页面
                toLogin((HttpServletRequest) requestWrapper, response);
            }
        
        return true;
    }

    // ...
}

然后,在你的控制器中,你可以这样获取requestWrapper并从中读取请求体:

@RestController
public class MyController {

    @PostMapping("/myEndpoint")
    public String myEndpoint(@RequestBody String body) {
        // 进行你的处理
        // ...
        return "success";
    }

    // ...
}

这个解决方案的关键在于创建了一个新的资源来存储请求体,并从这个新的资源中创建输入流或读取器。在BodyReaderHttpServletRequestWrapper的构造函数中,我首先调用了父类的构造函数,然后读取了请求体并将其存储在一个字节数组中。这个字节数组是一个新的资源,它独立于原始的HTTP请求输入流,所以我们可以多次读取它。

然后,我重写了getInputStream()和getReader()方法,使它们返回一个新的输入流或读取器。这些输入流或读取器是从存储请求体的字节数组中创建的,而不是从原始的HTTP请求输入流中创建的。这样,我们就可以在后续的处理中多次读取请求体,而不会受到HTTP请求输入流只能被读取一次的限制。

通过这种方式,我成功地解决了HTTP请求输入流只能读取一次的问题。希望这个解决方案也能帮助到遇到类似问题的你。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring BootHTTP请求的Body只能被读取一次,因此如果Filter已经读取了Body,那么Controller就无法再次读取Body了。为了解决这个问题,可以使用HttpServletRequestWrapper来包装HttpServletRequest,从而在Filter读取Body后,仍然可以将HttpServletRequest传递给Controller。 以下是一个示例代码: ```java public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); body = IOUtils.toByteArray(request.getInputStream()); } @Override public ServletInputStream getInputStream() throws IOException { return new ServletInputStreamWrapper(body); } @Override public BufferedReader getReader() throws IOException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body); return new BufferedReader(new InputStreamReader(byteArrayInputStream)); } } ``` 然后,在Filter使用BodyReaderHttpServletRequestWrapper来包装HttpServletRequest: ```java public class BodyReaderFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; BodyReaderHttpServletRequestWrapper requestWrapper = new BodyReaderHttpServletRequestWrapper(httpServletRequest); chain.doFilter(requestWrapper, response); } } ``` 这样,在Controller就可以正常获取到请求的Body了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值