构建可重复读取inputStream的request

2/27/2023 FilterHandlerInterceptorAdapter

# 需求背景

  • 日常开发中,我们会遇到这样一个情况,需要通过拦截器对请求接口时传递过来的参数做一个处理,像加密什么的,通过拦截器获取请求参数又分两种情况。

# 情况一 @RequestParam

  • 接口使用 @RequestParam 接收参数
  • 针对情况一,我们只需要在拦截器中通过request.getParameterMap() 来获得全部 Parameter 参数就可以了
public static Map<String, String> getParameterMapAll(HttpServletRequest request)
{
	Enumeration<String> parameters = request.getParameterNames();

	Map<String, String> params = new HashMap<>();
	while (parameters.hasMoreElements()) {
		String parameter = parameters.nextElement();
		String value = request.getParameter(parameter);
		params.put(parameter, value);
	}

	return params;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 情况二 @RequestBody

  • 当接口使用 @RequestBody 接收参数时,我们在拦截器中使用同样的方法获取参数,就会出现流已关闭的异常,也就导致参数读取失败了。
  • 这是因为 Spring 已经对 @RequestBody 提前进行处理,而 HttpServletReqeust 获取输入流时仅允许读取一次,所以会报java.io.IOException: Stream closed
  • 解决方案就是去构建 可重复读取inputStreamrequest

# 定义过滤器

/**
 * Repeatable 过滤器
 * 
 */
public class RepeatableFilter implements Filter
{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException
    {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
    {
        ServletRequest requestWrapper = null;
        if (request instanceof HttpServletRequest
                && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE))
        {
            requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
        }
        //获取请求中的流,将取出来的字符串,再次转换成流,然后把它放入到新 request对象中
        // 在chain.doFiler方法中传递新的request对象
        if (null == requestWrapper)
        {
            chain.doFilter(request, response);
        }
        else
        {
            chain.doFilter(requestWrapper, response);
        }
    }

    @Override
    public void destroy()
    {

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 构建可重复读取inputStream的request

/**
 * 构建可重复读取inputStream的request
 * 解决: request.getInputStream()只能读取一次的问题
 */
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper
{
    private final byte[] body;

    public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException
    {
        super(request);
        request.setCharacterEncoding(Constants.UTF8);
        response.setCharacterEncoding(Constants.UTF8);

        body = HttpHelper.getBodyString(request).getBytes(Constants.UTF8);
    }

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

    @Override
    public ServletInputStream getInputStream() throws IOException
    {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream()
        {
            @Override
            public int read() throws IOException
            {
                return bais.read();
            }

            @Override
            public int available() throws IOException
            {
                return body.length;
            }

            @Override
            public boolean isFinished()
            {
                return false;
            }

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

            @Override
            public void setReadListener(ReadListener readListener)
            {

            }
        };
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

# 通用http工具封装

/**
 * 通用http工具封装
 * 
 */
public class HttpHelper
{
    private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class);

    /**
	* 获取body参数
	* @param request
	*/
    public static String getBodyString(ServletRequest request)
    {
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;
        try (InputStream 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)
        {
            LOGGER.warn("getBodyString出现问题!");
        }
        finally
        {
            if (reader != null)
            {
                try
                {
                    reader.close();
                }
                catch (IOException e)
                {
                    LOGGER.error(ExceptionUtils.getMessage(e));
                }
            }
        }
        return sb.toString();
    }

    /**
	* 获取query参数
	* @param request
	*/
	public static Map<String, String> getParameterMapAll(HttpServletRequest request) {
		Enumeration<String> parameters = request.getParameterNames();

		Map<String, String> params = new HashMap<>();
		while (parameters.hasMoreElements()) {
			String parameter = parameters.nextElement();
			String value = request.getParameter(parameter);
			params.put(parameter, value);
		}

		return params;
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

# 使用

  • 首先判断本次 request 请求对象是不是 RepeatedlyRequestWrapper 类的实例,如果是,则代表本次请求的接口是使用 @RequestBody 来接收的参数,那么我们就需要用 getBodyString() 方法获取参数;反之,则代表接口使用 @RequestParam 接收参数,我们直接用 request.getParameterMap() 来获得全部参数即可。
// 伪代码如下
public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation)
{
    String nowParams = "";
    if (request instanceof RepeatedlyRequestWrapper)
    {
        RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
        nowParams = HttpHelper.getBodyString(repeatedlyRequest);
    }

    // body参数为空,获取Parameter的数据
    if (StringUtils.isEmpty(nowParams))
    {
        nowParams = JSON.toJSONString(request.getParameterMap());
    }
    return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17