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