执子之手

与子偕老


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于

  • 搜索
close

使用HttpServletRequestWrapper解决无法多次获取request Body的问题

时间: 2020-04-30   |   分类: 开发     |   阅读: 1866 字 ~4分钟   |   访问: 0

在使用AOP编程的时候,经常碰到需要多次获取整个请求的body的情况。例如:典型场景下我们要在AOP切面中做日志记录或权限校验,此时需要调用request.getInputStream获取输入流,从而读取整个请求的消息体。但是这通常会触发一个异常:java.lang.IllegalStateException: getInputStream() can't be called after getReader()。

出现这个问题的原因是默认的HttpServletRequest对象中的getInputStream,getReader函数式只允许调用一次。在一次请求中,除了我们在切面中调用getInputStream之外,Spring MVC框架在进行参数转换的时候还需要调用getInputStream方法读取整个请求的消息体,然后转回为请求参数,这违背了只调用一次的原则,从而触发了以异常,

为了解决这个问题,我们可以引入HttpServletRequestWrapper这个对象。这个类封装了HttpServletRequest的行为,我们可以继承这个类,从而使用一个新类模拟原始HttpServletRequest的行为。然后使用过滤器(filter)将原始的HttpServletRequest对象替换为HttpServletRequestWrapper对象。

最近在项目中有需求为API请求增加参数签名校验,使用了AOP切面功能,因此碰到了上面的问题:参数校验切面中需要在读取整个请求报文,然后对报文进行hmac算法从而计算签名值。下面说一下具体的解决办法,以代码为主。

1. 相关代码

1.1 RequestWrapper

RequestWrapper继承了HttpServletRequestWrapper,初始化的时候读取Request的整个请求体。然后重载了getInputStream和getReader方法,这两个方法会从类变量body中读取内容。

 1public class RequestWrapper extends HttpServletRequestWrapper {
 2    private final String body;
 3
 4    public RequestWrapper(HttpServletRequest request) throws IOException
 5    {
 6        //So that other request method behave just like before
 7        super(request);
 8
 9        StringBuilder stringBuilder = new StringBuilder();
10        BufferedReader bufferedReader = null;
11        try {
12            InputStream inputStream = request.getInputStream();
13            if (inputStream != null) {
14                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
15                char[] charBuffer = new char[128];
16                int bytesRead = -1;
17                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
18                    stringBuilder.append(charBuffer, 0, bytesRead);
19                }
20            } else {
21                stringBuilder.append("");
22            }
23        } catch (IOException ex) {
24            throw ex;
25        } finally {
26            if (bufferedReader != null) {
27                try {
28                    bufferedReader.close();
29                } catch (IOException ex) {
30                    throw ex;
31                }
32            }
33        }
34        //Store request pody content in 'body' variable
35        body = stringBuilder.toString();
36    }
37
38    @Override
39    public ServletInputStream getInputStream() throws IOException {
40        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
41        ServletInputStream servletInputStream = new ServletInputStream() {
42            @Override
43            public boolean isFinished() {
44                return false;
45            }
46
47            @Override
48            public boolean isReady() {
49                return true;
50            }
51
52            @Override
53            public void setReadListener(ReadListener readListener) {
54                throw new UnsupportedOperationException();
55            }
56
57            public int read() throws IOException {
58                return byteArrayInputStream.read();
59            }
60        };
61        return servletInputStream;
62    }
63
64    @Override
65    public BufferedReader getReader() throws IOException {
66        return new BufferedReader(new InputStreamReader(this.getInputStream()));
67    }
68
69    //Use this method to read the request body N times
70    public String getBody() {
71        return this.body;
72    }
73}

1.2 RequestWrapperFilter

RequestWrapperFilter是一个过滤器,它完成将普通的HttpServletRequest转化为RequestWrapper对象的工作。使用时需要使用该filter过滤需要添加校验的url。

 1public class RequestWrapperFilter implements Filter {
 2    private FilterConfig filterConfig = null;
 3
 4    @Override
 5    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
 6        servletRequest = new RequestWrapper((HttpServletRequest) servletRequest);
 7        //Read request.getBody() as many time you need
 8        filterChain.doFilter(servletRequest, servletResponse);
 9    }
10
11    @Override
12    public void init(FilterConfig filterConfiguration) throws ServletException {
13        this.filterConfig = filterConfiguration;
14    }
15
16    @Override
17    public void destroy() {
18        this.filterConfig = null;
19    }
20}

1.3 Configuration

在配置类中需要使用FilterRegisterationBean对过滤器进行配置。这里配置过滤url为:/api/dpp/*。

 1    @Bean(name="apiAuthFilter")
 2    public RequestWrapperFilter apiAuthFilter() {
 3        return new RequestWrapperFilter();
 4    }
 5
 6    @Bean(name="apiAuthBean")
 7    public FilterRegistrationBean apiAuthBean() {
 8        FilterRegistrationBean registration = new FilterRegistrationBean(apiAuthFilter());
 9        registration.setOrder(1);
10        registration.addUrlPatterns("/api/dpp/*");
11        return registration;
12    }

1.4 ApiAuthAspect

ApiAuthAspect完成签名的校验功能。在该类中使用了Spring的RequestContextHolder类获取当前请求对应的HttpServletRequest。注意:这里获取到的对象中实际包含的对象是RequestWrapper对象。 因为是已经重新包装过的RequestWrapper对象,所以可以再次调用getReader,getInputStream方法以获取整个消息体。

需要注意的是:获取到的HttpServletRequest对象无法直接进行类型转换,转换为RequestWrapper,类型不匹配。猜测是容器封装了多次的原因。有时间再仔细研究。

 1/**
 2     * 获取当前请求的HttpServletRequest对象
 3     * @return
 4     */
 5    protected HttpServletRequest getHttpServletRequest() {
 6        return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
 7    }
 8
 9    protected String getRequestBody(HttpServletRequest request) {
10        StringBuffer sb = new StringBuffer();
11        BufferedReader bufferedReader = null;
12        String content = "";
13
14        try {
15            //InputStream inputStream = request.getInputStream();
16            //inputStream.available();
17            //if (inputStream != null) {
18            bufferedReader =  request.getReader() ; //new BufferedReader(new InputStreamReader(inputStream));
19            char[] charBuffer = new char[128];
20            int bytesRead;
21            while ( (bytesRead = bufferedReader.read(charBuffer)) != -1 ) {
22                sb.append(charBuffer, 0, bytesRead);
23            }
24            //} else {
25            //        sb.append("");
26            //}
27
28        } catch (IOException ex) {
29            logger.error("Failed to getInputStream.", ex);
30        } finally {
31            if (bufferedReader != null) {
32                try {
33                    bufferedReader.close();
34                } catch (IOException ex) {
35                    logger.error("Failed to getInputStream.", ex);
36                }
37            }
38        }
39
40        return sb.toString();
41    }
42
43    public Object apiAuthAround(ProceedingJoinPoint joinPoint) throws Throwable {
44        Object request = joinPoint.getArgs()[0];
45        MethodSignature signature = ((MethodSignature) joinPoint.getSignature());
46
47        HttpServletRequest httpRequest = getHttpServletRequest();
48        String signatureType = httpRequest.getHeader("x-signature-type");
49        String signatureData = httpRequest.getHeader("x-signature-data");
50        String requestBody = getRequestBody(httpRequest);
51
52        // 校验
53    }

附录、参考资料

  • How to read request.getInputStream() multiple times
  • HttpServletRequestWrapper example – read httpservletrequest twice
#Java# #Spring#
如何在Mac上编译fheroes2
Linux常用命令介绍 04 - journalctl
  • 文章目录
  • 站点概览
Orchidflower

Orchidflower

Do one thing at a time, and do well.

77 日志
6 分类
84 标签
GitHub 知乎 OSC 豆瓣
  • 1. 相关代码
    • 1.1 RequestWrapper
    • 1.2 RequestWrapperFilter
    • 1.3 Configuration
    • 1.4 ApiAuthAspect
  • 附录、参考资料
© 2009 - 2024 执子之手
Powered by - Hugo v0.113.0
Theme by - NexT
ICP - 鲁ICP备17006463号-1
0%