解决HttpServletRequest流数据不可重复读
背景介绍
甲方客户的生产系统,有安全风险预警和安全事件快速溯源要求,需要做一套日志管理规范。
要求我们接入的系统,要对用户登录、注册、密码修改等重要场景,严格按照提供的格式,输出相应的日志。
后续通过filebeat对接,收集我们系统上的日志信息。
简单来说,就是应用系统,处理接口请求时,统一打印相应日志。问题描述
成熟且常见的日志统一打印方案,就是使用AOP技术,自定义注解,在切面上使用环绕通知Around,拦截请求,获取Controller类上方法的入参、出参即可。
奈何业务场景使用到的接口,以前的人在实现的时候,使用了如下方式:RequestMapping(valueauth,method{RequestMethod。POST,RequestMethod。GET,RequestMethod。OPTIONS})publicvoidauth(HttpServletRequestreq,HttpServletResponseresp){authService。auth(req,resp);}
把传参直接丢在HttpServletRequest中。
返回参数,又是采用HttpServletResponse输出。publicvoidprintResult(HttpServletRequestreq,HttpServletResponseresp,Stringaction,intcode,Stringmsg,Objectresult){PrintWriterpnull;RetretnewRet();GsongsonnewGsonBuilder()。setDateFormat(yyyyMMddHH:mm:ss)。serializeNulls()。create();try{presp。getWriter();ret。setRspCode(code);ret。setRspDesc(msg);ret。setData(result);p。write(gson。toJson(ret));return;}catch(Exceptione){logger。error(e。getMessage());}finally{p。flush();p。close();}}
不像平时熟练的做法,把具体入参和出参,用对象封装,直接放在方法上即可。
因为上面的做法,导致我们在拦截器中,想提前拦截请求获取传参,使用request。getParameter()等方法时,能拿到参数。但是在具体接口业务流程中,再使用request。getParameter()等方法,传入参数就获取不到了。
因为流只能被读一次。
因此就抛出一个问题:Request和Response怎么重复读取?解决方案
使用request。getParameter()等方法,最终会调用getInputStream方法。
需要重写HttpServletRequestWrapper包装类,在调用getInputStream方法时,将流数据同时写到缓存。
后面想获取参数,直接读取缓存数据即可。
这样就可以实现Request的内容多次读取。实现代码封装request
自定义类ContentCachingRequestWrapperimportjavax。servlet。ReadListener;importjavax。servlet。ServletInputStream;importjavax。servlet。http。HttpServletRequest;importjavax。servlet。http。HttpServletRequestWrapper;importjava。io。BufferedReader;importjava。io。ByteArrayInputStream;importjava。io。IOException;importjava。io。InputStreamReader;importjava。nio。charset。StandardCharsets;重写HttpServletRequestWrapperAuthor:linzengruiDate:2021112215:33publicclassContentCachingRequestWrapperextendsHttpServletRequestWrapper{privatefinalbyte〔〕body;publicContentCachingRequestWrapper(HttpServletRequestrequest){super(request);StringBuildersbnewStringBuilder();try(BufferedReaderreadernewBufferedReader(newInputStreamReader(request。getInputStream(),StandardCharsets。UTF8))){Stringline;while((linereader。readLine())!null){sb。append(line);}}catch(IOExceptione){e。printStackTrace();}bodysb。toString()。getBytes(StandardCharsets。UTF8);}OverridepublicBufferedReadergetReader()throwsIOException{returnnewBufferedReader(newInputStreamReader(getInputStream()));}OverridepublicServletInputStreamgetInputStream()throwsIOException{finalByteArrayInputStreaminputStreamnewByteArrayInputStream(body);returnnewServletInputStream(){OverridepublicbooleanisFinished(){returnfalse;}OverridepublicbooleanisReady(){returnfalse;}OverridepublicvoidsetReadListener(ReadListenerreadListener){}Overridepublicintread()throwsIOException{returninputStream。read();}};}publicbyte〔〕getBody(){returnbody;}}封装response
自定义类ContentCachingResponseWrapperimportjavax。servlet。ServletOutputStream;importjavax。servlet。WriteListener;importjavax。servlet。http。HttpServletResponse;importjavax。servlet。http。HttpServletResponseWrapper;importjava。io。;重写HttpServletResponseWrapperAuthor:linzengruiDate:2021112219:45publicclassContentCachingResponseWrapperextendsHttpServletResponseWrapper{privateByteArrayOutputStreambyteArrayOutputStream;privateServletOutputStreamservletOutputStream;privatePrintWriterprintWriter;publicContentCachingResponseWrapper(HttpServletResponseresponse){super(response);byteArrayOutputStreamnewByteArrayOutputStream();servletOutputStreamnewServletOutputStream(){OverridepublicbooleanisReady(){returnfalse;}OverridepublicvoidsetWriteListener(WriteListenerwriteListener){}Overridepublicvoidwrite(intb)throwsIOException{byteArrayOutputStream。write(b);}};printWriternewPrintWriter(byteArrayOutputStream);}OverridepublicPrintWritergetWriter(){returnprintWriter;}OverridepublicServletOutputStreamgetOutputStream()throwsIOException{returnservletOutputStream;}publicbyte〔〕toByteArray(){returnbyteArrayOutputStream。toByteArray();}}过滤器Filter拦截请求
拦截器LogFilter使用上面封装的包装类,即可获取传参。Slf4jWebFilter(urlPatterns)publicclassLogFilterimplementsFilter{Overridepublicvoidinit(FilterConfigfilterConfig)throwsServletException{}OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{使用重写HttpServletRequestWrapper的自定义包装类ContentCachingRequestWrapperrequestWrappernewContentCachingRequestWrapper((HttpServletRequest)request);使用重写HttpServletResponseWrapper的自定义包装类ContentCachingResponseWrapperresponseWrappernewContentCachingResponseWrapper((HttpServletResponse)response);只能执行一次获取流方法try(ServletOutputStreamoutputStreamresponse。getOutputStream()){获取传参StringrequestParamJsonnewString(requestWrapper。getBody());log。info(requestParamJson{},requestParamJson);具体方法执行流程chain。doFilter(requestWrapper,responseWrapper);触发获取流操作后,可以从缓存多次拿数据StringrespDataJsonnewString(responseWrapper。toByteArray());log。info(respDataJson{},respDataJson);TODO写日志需要重新写入内容,否则流无输出内容outputStream。write(respDataJson。getBytes(StandardCharsets。UTF8));outputStream。flush();}catch(Exceptione){e。printStackTrace();}}Overridepublicvoiddestroy(){}}springboot启动类添加注解ServletComponentScan
注意:启动类要加上注解ServletComponentScan识别上面注入的Filter。importorg。springframework。boot。SpringApplication;importorg。springframework。boot。web。servlet。ServletComponentScan;ServletComponentScanSpringBootApplicationpublicclassSpringBootApplication{publicstaticvoidmain(String〔〕args){SpringApplication。run(SpringBootApplication。class,args);}}
具体业务接口,原来的逻辑保持不变,仍然可以获取到入参。
郭德纲于谦私下不来往是真的吗,两人私下关系怎么样郭德纲和于谦是相声演出的搭档,在台上默契十足,给大家带来了很多欢乐,但是他们俩私下却很少往来,则被外界当成了不往来。对于私下不往来的原因早在他们俩做客《鲁豫有约》时就曝光过原因……
只带手机不带手机卡能查到我的行踪轨迹吗,有何方法?只带手机但是不插电话卡,原则上讲是不好查找你的位置的,但如果你的手机连接了网络,则是可以查找到你的踪迹的。大家知道,手机插上手机卡以后就可以打电话、发信息,而且手机卡还有……
桃子奶赵茹珍个人资料三维尽现的云画得看赵茹珍是韩国著名的女演员,尽管她出道的时候是以模特的身份出现在杂志上,但是一点都没有影响她后来的影视发展道路,《云画的月光》当中桃子奶赵茹珍大尺度献镜,她个人资料是这样的,赵茹……
方子传赵茹珍被动的画面画面太辣眼却又忍不住直视可能很多人对于三影片还限制于现代版的印象当中。要知道古装的三片可是丝毫不逊色的。这方面在韩国影片中尤为突出。韩国编剧喜欢用古装来诠释情爱。所以韩国三片很多都是古装剧情。但是尺度……
赵茹珍演的三片韩国国宝级女艺人演技很好赵茹珍在韩国是非常知名的女艺人,即使是赵茹珍演绎了什么三级电影,但是在韩国的地位还是很高的啊!演技什么在韩国是有着自己的特色,很多人对于赵茹珍的评价也是很高,只是说赵茹珍演绎了……
纪文君球球gif陈翔六点半两个人身材最美丽纪文君和球球两个人应该是视频《陈翔六点半》中最耀眼的两个人,因为在这个视频中看到的一些女孩子颜值、身材都很好,不过网络中也是出现了纪文君和球球两个人的动图,内容是什么,网友是多……
纪文君球球被后啪身材太好总是冒充她纪文君就是球球,其实在这个视频团队中非常不错,为了今天其实是付出了不知道多少,人们认识她是因为什么,是因为她的身材很好,在视频中也秀出了自己好身材,绝对是被人们认可和喜欢的对象……
纪文君被禁的图片有资本还不让人炫耀了作为艺人想要走红。并不像之前那样只能通过拍摄一些大片或者是知名的电视剧才能够走红。现在可谓是走红的方式千千万,谁说网剧就不能捧红演员呢。甚至是现在大家熟悉的直播行业。这些都是能……
顺丰快递员一天大概有多少收入,有图有真相大家好,我是快递小哥小唐,上一个文章给大家讲解了一下顺丰快递的福利和待遇的情况,今天来说说大多数顺丰快递员一天可以跑到多少单,一单多少钱,一天下来能挣多少钱!这几天江阴这……
golang从入门到精通,模板内置函数自定义函数管道符的使用这篇文章将学习模板内置函数的使用、自定义函数的使用、管道符的使用D:gosrcgo7demo1main。go源码及解析本节将学习模板内置函数的使用、自定义函数的使用、管道符的使……
曝MIUI13将大改!这次还会辜负米粉们的期望吗?目前各大手机厂商都在着重打造自家的操系统,像魅族的Flyme和OPPO的ColorOS今年都取得非常不错的口碑反而今年小米MIUI12。5的表现只能用差强人意来形容,而且小米也……
营销中台建设(十)揭秘电商中台的换货玄学前两期营销中台建设(八):订单履约,以客户为导向的端到端流程、营销中台建设(九):全程透明的售后服务体系,整体介绍了美云智数电商中台正逆向流程,本文深入解构交易中心换货流程,探……