服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - SpringBoot通过请求对象获取输入流无数据

SpringBoot通过请求对象获取输入流无数据

2022-10-08 15:24蒋固金 Java教程

这篇文章主要介绍了使用SpringBoot通过请求对象获取输入流无数据,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

请求对象获取输入流无数据问题

昨天下午在开发的时候遇到了奇怪的事情,在SpringBoot的Controller里面直接使用HttpServletRequest的getInputStream()方法的时候获得的输入流无数据,通过getContentLength()获得内容长度的时候又是有值的,由于昨天比较晚了就没有研究,今天花了点时间查一下原因。

出现这种情况,首先怀疑输入流已经被使用了,由于请求输入流是不带缓存的,使用一次后流就无效了,通常触发解析输入流就是调用了getParameter()等方法,经过检查代码确认没有做过相关处理,所以怀疑SpringBoot底层做了处理。

查了一下SpringBoot的自动装配配置,在WebMvcAutoConfiguration中初始化了一个OrderedHiddenHttpMethodFilter,默认这个过滤器是生效的,相关代码如下:

?
1
2
3
4
5
6
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = true)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter();
}

OrderedHiddenHttpMethodFilter继承了OrderedHiddenHttpMethodFilter,而OrderedHiddenHttpMethodFilter又继承了HiddenHttpMethodFilter,在该类的doFilterInternal()方法中发现有对参数做处理,相关代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {
    HttpServletRequest requestToUse = request;
    if ("POST".equals(request.getMethod()) && 
    request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
        String paramValue = request.getParameter(this.methodParam);
        if (StringUtils.hasLength(paramValue)) {
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            if (ALLOWED_METHODS.contains(method)) {
                requestToUse = new HttpMethodRequestWrapper(request, method);
            }
        }
    }
    filterChain.doFilter(requestToUse, response);
}

至此就可以定位问题的所在了,找到了问题下面就来看看如何解决。

方案一:禁用默认的过滤器

SpringBoot在自动装配的时候注入了OrderedHiddenHttpMethodFilter,如果我们不需要该功能,在配置文件中显示的将其设置为false。配置如下:

?
1
spring.mvc.hiddenmethod.filter.enabled=false

方案二:使用@RequestBody注解

在需要获取请求输入流的方法上添加字节数组的参数,并添加@RequestBody注解,示例代码如下:

?
1
2
3
4
@RequestMapping("/**")
public 返回值 方法名(@RequestBody byte[] body) {
   // ...
}

方案三:自定义HiddenHttpMethodFilter过滤器

参考代码如下:

?
1
2
3
4
5
6
7
8
9
10
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter(){
        @Override
        protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
            filterChain.doFilter(request, response);
        }
    };

request输入流重复可读

自定义类继承 HttpServletRequestWrapper

?
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
/**
 * @describe 目的是让其输入流可重复读
 * @author czx
 * @date 2020年5月15日22:53:35
 */
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {
    /**
     * 存储body数据的容器
     */
    private final byte[] body;
 
    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
 
        // 将body数据存储起来
        String bodyStr = getBodyString(request);
        body = bodyStr.getBytes(Charset.defaultCharset());
    }
 
    /**
     * 获取请求Body
     *
     * @param request request
     * @return String
     */
    public String getBodyString(final ServletRequest request) {
        try {
            return inputStream2String(request.getInputStream());
        } catch (IOException e) {
            log.error("", e);
            throw new RuntimeException(e);
        }
    }
 
    /**
     * 获取请求Body
     *
     * @return String
     */
    public String getBodyString() {
        final InputStream inputStream = new ByteArrayInputStream(body); 
        return inputStream2String(inputStream);
    }
 
    /**
     * 将inputStream里的数据读取出来并转换成字符串
     *
     * @param inputStream inputStream
     * @return String
     */
    private String inputStream2String(InputStream inputStream) {
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;
 
        try {
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            log.error("", e);
            throw new RuntimeException(e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    log.error("", e);
                }
            }
        }
 
        return sb.toString();
    }
 
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
 
    @Override
    public ServletInputStream getInputStream() throws IOException {
 
        final ByteArrayInputStream inputStream = new ByteArrayInputStream(body); 
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return inputStream.read();
            }
 
            @Override
            public boolean isFinished() {
                return false;
            }
 
            @Override
            public boolean isReady() {
                return false;
            }
 
            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
   
}

定义一个过滤器 Filter

?
1
2
3
4
5
6
7
8
9
@Slf4j
public class ReplaceStreamFilter implements Filter {
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = new RequestWrapper((HttpServletRequest) request);
        chain.doFilter(requestWrapper, response);
    }
}

创建过滤器配置类 FilterConfig

?
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
@Configuration
public class FilterConfig {
 
    /**
     * 注册过滤器
     *
     * @return FilterRegistrationBean
     */
    @Bean
    public FilterRegistrationBean someFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(replaceStreamFilter());
        registration.addUrlPatterns("/*");
        registration.setName("streamFilter");
        return registration;
    }
 
    /**
     * 实例化StreamFilter
     * @return Filter
     */
    @Bean(name = "replaceStreamFilter")
    public Filter replaceStreamFilter() {
        return new ReplaceStreamFilter();
   
}

完成以上步骤即可在拦截器中读取request中的body数据 

?
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
@Slf4j
@Component
public class APIInterceptor implements HandlerInterceptor { 
    /**
     * 预处理回调方法,实现处理器的预处理
     * 返回值:true表示继续流程;false表示流程中断,不会继续调用其他的拦截器或处理器
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
        log.info("开始拦截请求");
 
        if(isJson(request)){
            String jsonParam = new RequestWrapper(request).getBodyString();
            JSONObject params = JSONObject.parseObject(jsonParam);
            ......
            return true;
        }        
        return false;
    }   
 
    /**
     * 返回json数据给前端
     * @param response
     * @param json
     */
    protected void returnJson(ServletResponse response, JSONObject json){
        PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        try {
            writer = response.getWriter();
            writer.print(json.toJSONString());
 
        } catch (IOException e) {
            log.error("response error",e);
        } finally {
            if (writer != null)
                writer.close();
        }
    }  
 
    /**
     * 判断本次请求的数据类型是否为json
     * @param request request
     * @return boolean
     */
    private boolean isJson(HttpServletRequest request) {
        if (request.getContentType() != null) {
            return request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) ||
                    request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE);
       
        return false;
    }

以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/jianggujin/article/details/86644914

延伸 · 阅读

精彩推荐