SpringBoot通過請求對象獲取輸入流無數(shù)據(jù)
請求對象獲取輸入流無數(shù)據(jù)問題
昨天下午在開發(fā)的時候遇到了奇怪的事情,在SpringBoot的Controller里面直接使用HttpServletRequest的getInputStream()方法的時候獲得的輸入流無數(shù)據(jù),通過getContentLength()獲得內(nèi)容長度的時候又是有值的,由于昨天比較晚了就沒有研究,今天花了點(diǎn)時間查一下原因。
出現(xiàn)這種情況,首先懷疑輸入流已經(jīng)被使用了,由于請求輸入流是不帶緩存的,使用一次后流就無效了,通常觸發(fā)解析輸入流就是調(diào)用了getParameter()等方法,經(jīng)過檢查代碼確認(rèn)沒有做過相關(guān)處理,所以懷疑SpringBoot底層做了處理。
查了一下SpringBoot的自動裝配配置,在WebMvcAutoConfiguration中初始化了一個OrderedHiddenHttpMethodFilter,默認(rèn)這個過濾器是生效的,相關(guān)代碼如下:
@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()方法中發(fā)現(xiàn)有對參數(shù)做處理,相關(guān)代碼如下:
@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); }
至此就可以定位問題的所在了,找到了問題下面就來看看如何解決。
方案一:禁用默認(rèn)的過濾器
SpringBoot在自動裝配的時候注入了OrderedHiddenHttpMethodFilter,如果我們不需要該功能,在配置文件中顯示的將其設(shè)置為false。配置如下:
spring.mvc.hiddenmethod.filter.enabled=false
方案二:使用@RequestBody注解
在需要獲取請求輸入流的方法上添加字節(jié)數(shù)組的參數(shù),并添加@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輸入流重復(fù)可讀
自定義類繼承 HttpServletRequestWrapper
/** ?* @describe 目的是讓其輸入流可重復(fù)讀 ?* @author czx ?* @date 2020年5月15日22:53:35 ?*/ @Slf4j public class RequestWrapper extends HttpServletRequestWrapper { ? ? /** ? ? ?* 存儲body數(shù)據(jù)的容器 ? ? ?*/ ? ? private final byte[] body; ? ? ? public RequestWrapper(HttpServletRequest request) throws IOException { ? ? ? ? super(request); ? ? ? ? ? // 將body數(shù)據(jù)存儲起來 ? ? ? ? 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里的數(shù)據(jù)讀取出來并轉(zhuǎn)換成字符串 ? ? ?* ? ? ?* @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); ? ? } }
創(chuàng)建過濾器配置類 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數(shù)據(jù)
@Slf4j @Component public class APIInterceptor implements HandlerInterceptor {? ? ? /** ? ? ?* 預(yù)處理回調(diào)方法,實現(xiàn)處理器的預(yù)處理 ? ? ?* 返回值:true表示繼續(xù)流程;false表示流程中斷,不會繼續(xù)調(diào)用其他的攔截器或處理器 ? ? ?*/ ? ? @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數(shù)據(jù)給前端 ? ? ?* @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(); ? ? ? ? } ? ? }?? ? ? ? /** ? ? ?* 判斷本次請求的數(shù)據(jù)類型是否為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; ? ? }
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot?將配置文件掛到?jar?包外面的操作方法
在 SpringBoot 中,可以將配置文件放在 jar 包外面,這樣可以方便地修改配置而不需要重新打包和部署,這篇文章主要介紹了SpringBoot?如何將配置文件掛到?jar?包外面,需要的朋友可以參考下2023-03-03spring boot @ResponseBody轉(zhuǎn)換JSON 時 Date 類型處理方法【兩種方法】
這篇文章主要介紹了spring boot @ResponseBody轉(zhuǎn)換JSON 時 Date 類型處理方法,主要給大家介紹Jackson和FastJson兩種方式,每一種方法給大家介紹的都非常詳細(xì),需要的朋友可以參考下2018-08-08使用Spring?Boot如何限制在一分鐘內(nèi)某個IP只能訪問10次
有些時候,為了防止我們上線的網(wǎng)站被攻擊,或者被刷取流量,我們會對某一個ip進(jìn)行限制處理,這篇文章,我們將通過Spring?Boot編寫一個小案例,來實現(xiàn)在一分鐘內(nèi)同一個IP只能訪問10次,感興趣的朋友一起看看吧2023-10-10springMVC使用ajaxFailUpload上傳圖片的方法
這篇文章主要介紹了springMVC使用ajaxFailUpload上傳圖片的相關(guān)知識,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-06-06Mybatis日志配置方式(slf4j、log4j、log4j2)
這篇文章主要介紹了Mybatis日志配置方式(slf4j、log4j、log4j2),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09