请求对象获取输入流无数据问题
昨天下午在开发的时候遇到了奇怪的事情,在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
查看更多关于SpringBoot通过请求对象获取输入流无数据的详细内容...