




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
解決HttpServletRequest的輸入流只能讀取一次的問題背景通常對安全性有要求的接口都會(huì)對請求參數(shù)做一些簽名驗(yàn)證,而我們一般會(huì)把驗(yàn)簽的邏輯統(tǒng)一放到過濾器或攔截器里,這樣就不用每個(gè)接口都去重復(fù)編寫驗(yàn)簽的邏輯。在一個(gè)項(xiàng)目中會(huì)有很多的接口,而不同的接口可能接收不同類型的數(shù)據(jù),例如表單數(shù)據(jù)和json數(shù)據(jù),表單數(shù)據(jù)還好說,調(diào)用request的getParameterMap就能全部取出來。而json數(shù)據(jù)就有些麻煩了,因?yàn)閖son數(shù)據(jù)放在body中,我們需要通過request的輸入流去讀取。但問題在于request的輸入流只能讀取一次不能重復(fù)讀取,所以我們在過濾器或攔截器里讀取了request的輸入流之后,請求走到controller層時(shí)就會(huì)報(bào)錯(cuò)。而本文的目的就是介紹如何解決在這種場景下遇到HttpServletRequest的輸入流只能讀取一次的問題。注:本文代碼基于SpringBoot框架HttpServletRequest的輸入流只能讀取一次的原因我們先來看看為什么HttpServletRequest的輸入流只能讀一次,當(dāng)我們調(diào)用getInputStream()方法獲取輸入流時(shí)得到的是一個(gè)InputStream對象,而實(shí)際類型是ServletInputStream,它繼承于InputStream。InputStream的read()方法內(nèi)部有一個(gè)postion,標(biāo)志當(dāng)前流被讀取到的位置,每讀取一次,該標(biāo)志就會(huì)移動(dòng)一次,如果讀到最后,read()會(huì)返回-1,表示已經(jīng)讀取完了。如果想要重新讀取則需要調(diào)用reset()方法,position就會(huì)移動(dòng)到上次調(diào)用mark的位置,mark默認(rèn)是0,所以就能從頭再讀了。調(diào)用reset()方法的前提是已經(jīng)重寫了reset()方法,當(dāng)然能否reset也是有條件的,它取決于markSupported()方法是否返回true。InputStream默認(rèn)不實(shí)現(xiàn)reset(),并且markSupported()默認(rèn)也是返回false,這一點(diǎn)查看其源碼便知:我們再來看看ServletInputStream,可以看到該類沒有重寫mark(),reset()以及markSupported()方法:綜上,InputStream默認(rèn)不實(shí)現(xiàn)reset的相關(guān)方法,而ServletInputStream也沒有重寫reset的相關(guān)方法,這樣就無法重復(fù)讀取流,這就是我們從request對象中獲取的輸入流就只能讀取一次的原因。使用HttpServletRequestWrapper+Filter解決輸入流不能重復(fù)讀取問題既然ServletInputStream不支持重新讀寫,那么為什么不把流讀出來后用容器存儲(chǔ)起來,后面就可以多次利用了。那么問題就來了,要如何存儲(chǔ)這個(gè)流呢?所幸JavaEE提供了一個(gè)HttpServletRequestWrapper類,從類名也可以知道它是一個(gè)http請求包裝器,其基于裝飾者模式實(shí)現(xiàn)了HttpServletRequest界面,
部分源碼如下:晶HttpServletRequestWrapper.javaConstructsarequestobjectwrappingthegivenreque^?p3廣日mrequestTherequesttowrap{gr/Tr^k-.?java,lang部分源碼如下:晶HttpServletRequestWrapper.javaConstructsarequestobjectwrappingthegivenreque^?p3廣日mrequestTherequesttowrap{gr/Tr^k-.?java,lang.IIIegaIArgumentExceptioniftherequestisnulI*Thedefaultbehaviorofthismethodistoreturn*wrappedrequestobject.2/.一一*rhedefaultbehaviorofthismethodistoreturngetC*wrappedrequestobject.^OverridepublicCookie[]getCookies()|{returnthis._getHttpServletRequest(),getCookies();)(aOverridepublicStringgetAuthTypef){returnthis._getHttpServletRequest().getAuthType();}一.public.HttpServletRequestWrapperfHttpServletRequestsuper(request);)*@sincev2.3*/;"publicclassHttpServletRequestWrapperextendsServletRequesHttpServletRequest{privateHttpServletRequest_getHttpServletRequest()return(HttpServletRequest)super.getRequest();)從上圖中的部分源碼可以看到,該類并沒有真正去實(shí)現(xiàn)HttpServletRequest的方法,而只是在方法內(nèi)又去調(diào)用HttpServletRequest的方法,所以我們可以通過繼承該類并實(shí)現(xiàn)想要重新定義的方法以達(dá)到包裝原生HttpServletRequest對象的目的。首先我們要定義一個(gè)容器,將輸入流里面的數(shù)據(jù)存儲(chǔ)到這個(gè)容器里,這個(gè)容器可以是數(shù)組或集合。然后我們重寫getInputStream方法,每次都從這個(gè)容器里讀數(shù)據(jù),這樣我們的輸入流就可以讀取任意次了。具體的實(shí)現(xiàn)代碼如下:packagecom.example.wrapperdemo.controller.wrapper;importlombok.extern.slf4j.Slf4j;importjavax.servlet.ReadListener;importjavax.servlet.ServletInputStream;importjavax.servlet.ServletRequest;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletRequestWrapper;importjava.io.*;importjava.nio.charset.Charset;/**@author01@programwrapper-demo@descrption包裝HttpServletRequest,目的是讓其輸入流可重復(fù)讀@create2018-12-2420:48@since1.0**/@Slf4jpublicclassRequestWrapperextendsHttpServletRequestWrapper{/**存儲(chǔ)body數(shù)據(jù)的容器/privatefinalbyte[]body;publicRequestWrapper(HttpServletRequestrequest)throwsIOException{super(request);}}}}//將body數(shù)據(jù)存儲(chǔ)起來StringbodyStr=getBodyString(request);body=bodyStr.getBytes(Charset.defaultCharset());}/*獲取請求Body*@paramrequestrequest@returnString/publicStringgetBodyString(finalServletRequestrequest){try{returninputStream2String(request.getInputStream());}catch(IOExceptione){log.error("",e);thrownewRuntimeException(e);}}/**獲取請求Bodyhttp://www.f-1.cc*@returnString/publicStringgetBodyString(){finalInputStreaminputStream=newByteArrayInputStream(body);returninputStream2String(inputStream);將inputstream里的數(shù)據(jù)讀取出來并轉(zhuǎn)換成字符串*@paraminputStreaminputStream@returnString*/privateStringinputStream2String(InputStreaminputStream){StringBuildersb=newStringBuilder();BufferedReaderreader=null;try{reader=newBufferedReader(newInputStreamReader(inputStream,Charset.defaultCharset()));Stringline;while((line=reader.readLine())!=null){sb.append(line);}}catch(IOExceptione){log.error("",e);thrownewRuntimeException(e);}finally{if(reader!=null){try{reader.close();}catch(IOExceptione){log.error("",e);}}returnsb.toString();@OverridepublicBufferedReadergetReader()throwsIOException{returnnewBufferedReader(newInputStreamReader(getInputStream()));}@OverridepublicServletInputStreamgetInputStream()throwsIOException{finalByteArrayInputStreaminputStream=newByteArrayInputStream(body);returnnewServletInputStream(){@Overridepublicintread()throwsIOException{returninputStream.read();}@OverridepublicbooleanisFinished(){returnfalse;}@OverridepublicbooleanisReady(){returnfalse;@OverridepublicvoidsetReadListener(ReadListenerreadListener){}};}}除了要寫一個(gè)包裝器外,我們還需要在過濾器里將原生的HttpServletRequest對象替換成我們的Requestwrapper對象,代碼如下:packagecom.example.wrapperdemo.controller.filter;importcom.example.wrapperdemo.controller.wrapper.RequestWrapper;importlombok.extern.slf4j.Slf4j;importjavax.servlet.*;importjavax.servlet.http.HttpServletRequest;importjava.io.IOException;/**@author01@programwrapper-demo?description替換HttpServletRequest@create2018-12-2421:04@since1.0*/@Slf4jpublicclassReplaceStreamFilterimplementsFilter{@Overridepublicvoidinit(FilterConfigfilterConfig)throwsServletException{("StreamFilter初始化...");}@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{ServletRequestrequestWrapper=newRequestWrapper((HttpServletRequest)request);chain.doFilter(requestWrapper,response);@Overridepublicvoiddestroy(){("StreamFilter銷毀...”);然后我們就可以在攔截器中愉快的獲取json數(shù)據(jù)也不慌controller層會(huì)報(bào)錯(cuò)了:erceptor;importcom.example.wrapperdemo.controller.wrapper.RequestWrapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.http.MediaType;importorg.springframework.web.servlet.HandlerInterceptor;importorg.springframework.web.servlet.ModelAndView;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;@author01@programwrapper-demo@description簽名攔截器@create2018-12-2421:08@since1.0*/@Slf4jpublicclassSignatureInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{("[preHandle]executing...requesturiis{}",request.getRequestURI());if(isJson(request)){/獲取json字符串StringjsonParam=newRequestWrapper(request).getBodyString();("[preHandle]json數(shù)據(jù):{}",jsonParam);//驗(yàn)簽邏輯...略...}returntrue;}@OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{}@OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{}/**判斷本次請求的數(shù)據(jù)類型是否涉。c*@paramrequestrequest@returnboolean/privatebooleanisJson(HttpServletRequestrequest){if(request.getContentType()!=null){returnrequest.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)||request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE);returnfalse;}}編寫完以上的代碼后,還需要將過濾器和攔截器在配置類中進(jìn)行注冊才會(huì)生效,過濾器配置類代碼如下:packagecom.example.wrapperdemo.config;importcom.example.wrapperdemo.controller.filter.ReplaceStreamFilter;importorg.springframework.boot.web.servlet.FilterRegistrationBean;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjavax.servlet.Filter;/*@author01@programwrapper-demo@description過濾器配置類@create2018-12-2421:06@since1.0*/@ConfigurationpublicclassFilterConfig{/**注冊過濾器*@returnFilterRegistrationBean*/@BeanpublicFilterRegistrationBeansomeFilterRegistration(){FilterRegistrationBeanregistration=newFilterRegistrationBean();registration.setFilter(replaceStreamFilter());registration.addUrlPatterns("/*");registration.setName("streamFilter");returnregistration;}/**實(shí)例化StreamFilter*@returnFilter/@Bean(name="replaceStreamFilter")publicFilterreplaceStreamFilter(){returnnewReplaceStreamFilter();}}攔截器配置類代碼如下:packagecom.example.wrapperdemo.config;erceptor.SignatureInterceptor;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*@author01@programwrapper-demo@description@create2018-12-2421:16@since1.0**/@ConfigurationpublicclassInterceptorConfigimplementsWebMvcConfigurer{@BeanpublicSignatureInterceptorgetSignatureInterceptor(){returnnewSignatureInterceptor();/***注冊攔截器*@paramregistryregistry*/@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(getSignatureInterceptor()).addPathPatterns("/**");接下來我們就可以測試一下在攔截器中讀取了輸入流后在controller層是否還能正常接收數(shù)據(jù),首先定義一個(gè)實(shí)體類,代碼如下:packagecom.example.wrapperdemo.param;importlombok.AllArgsConstructor;importlombok.Builder;importlombok.Data;importlombok.NoArgsConstructor;/***@author01*@programwrapper-demo*@description*@create2018-12-2421:11*@since1.0**/@Data@Builder@NoArgsConstructor@AllArgsConstructorpublicclassUserParam{privateStringuserName;privateStringphone;privateStringpassword;}然后寫一個(gè)簡單的Controller,代碼如下:packagecom.example.wrapperdemo.controller;importcom.example.wrapperdemo.param.User
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 二零二五年度茶樓租賃合同茶樓與茶藝培訓(xùn)學(xué)校合作框架協(xié)議
- 二零二五年度在線教育平臺師資聘用協(xié)議
- 食用菌種植技術(shù)服務(wù)合同
- 英語語法中的定語從句詳解:九年級英語語法基礎(chǔ)強(qiáng)化教案
- 幼兒園繪本閱讀感悟分享
- 產(chǎn)品分銷銷售服務(wù)條款及目標(biāo)協(xié)定
- 數(shù)據(jù)驅(qū)動(dòng)的環(huán)保產(chǎn)業(yè)發(fā)展戰(zhàn)略協(xié)議
- 提升職場技能與素質(zhì)
- 數(shù)理化習(xí)題集:高三化學(xué)知識點(diǎn)強(qiáng)化練習(xí)計(jì)劃
- 家電產(chǎn)品渠道經(jīng)銷協(xié)議
- 2025年黑龍江農(nóng)業(yè)工程職業(yè)學(xué)院單招職業(yè)適應(yīng)性測試題庫及答案1套
- 《勞動(dòng)法常識(第3版)》中職全套教學(xué)課件
- 2025年勞動(dòng)合同延期補(bǔ)充協(xié)議模板
- 2025年日歷表(含農(nóng)歷、節(jié)假日、記事、A4打印版)
- 《反家庭暴力》課件
- 二零二五年度房地產(chǎn)預(yù)售合同協(xié)議4篇
- 2025-2030年中國天線行業(yè)市場需求狀況規(guī)劃研究報(bào)告
- 2024年南京旅游職業(yè)學(xué)院高職單招職業(yè)技能測驗(yàn)歷年參考題庫(頻考版)含答案解析
- 如何提升自我管理能力
- 2025年潛江市城市建設(shè)發(fā)展集團(tuán)招聘工作人員【52人】高頻重點(diǎn)提升(共500題)附帶答案詳解
- 人教版(新)九年級下冊化學(xué)全冊教案教學(xué)設(shè)計(jì)及教學(xué)反思
評論
0/150
提交評論