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)文章
性能爆棚的實(shí)體轉(zhuǎn)換復(fù)制工具M(jìn)apStruct使用詳解
這篇文章主要為大家介紹了性能爆棚的實(shí)體轉(zhuǎn)換復(fù)制工具M(jìn)apStruct使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
java_時(shí)間戳與Date_相互轉(zhuǎn)化的實(shí)現(xiàn)代碼
本篇文章是對(duì)java_時(shí)間戳與Date_相互轉(zhuǎn)化的實(shí)現(xiàn)代碼進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下如下。2016-11-11
springboot 設(shè)置server.port不生效的原因及解決
這篇文章主要介紹了springboot 設(shè)置server.port不生效的原因及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
SpringBoot項(xiàng)目中新增脫敏功能的實(shí)例代碼
項(xiàng)目中,由于使用端有兩個(gè),對(duì)于兩個(gè)端的數(shù)據(jù)權(quán)限并不一樣。Web端可以查看所有數(shù)據(jù),小程序端只能查看脫敏后的數(shù)據(jù),這篇文章主要介紹了SpringBoot項(xiàng)目中新增脫敏功能,需要的朋友可以參考下2022-11-11
java Comparator.comparing排序使用示例
本文主要介紹了java Comparator.comparing排序使用示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
springcloud?如何解決微服務(wù)之間token傳遞問(wèn)題
這篇文章主要介紹了springcloud?如何解決微服務(wù)之間token傳遞問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03

