SpringBoot通过请求对象获取输入流无数据
作者:蒋固金
请求对象获取输入流无数据问题
昨天下午在开发的时候遇到了奇怪的事情,在SpringBoot的Controller里面直接使用HttpServletRequest的getInputStream()方法的时候获得的输入流无数据,通过getContentLength()获得内容长度的时候又是有值的,由于昨天比较晚了就没有研究,今天花了点时间查一下原因。
出现这种情况,首先怀疑输入流已经被使用了,由于请求输入流是不带缓存的,使用一次后流就无效了,通常触发解析输入流就是调用了getParameter()等方法,经过检查代码确认没有做过相关处理,所以怀疑SpringBoot底层做了处理。
查了一下SpringBoot的自动装配配置,在WebMvcAutoConfiguration中初始化了一个OrderedHiddenHttpMethodFilter,默认这个过滤器是生效的,相关代码如下:
@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()方法中发现有对参数做处理,相关代码如下:
@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。配置如下:
spring.mvc.hiddenmethod.filter.enabled=false
方案二:使用@RequestBody注解
在需要获取请求输入流的方法上添加字节数组的参数,并添加@RequestBody注解,示例代码如下:
@RequestMapping("/**") public 返回值 方法名(@RequestBody byte[] body) { // ... }
方案三:自定义HiddenHttpMethodFilter过滤器
参考代码如下:
@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
/** * @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
@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
@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数据
@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; }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。