最近收到一个需求,出于审计的目的,希望可以通过日志记录下对应用程序发起的post、put请求的body内容,面对这样的一个需求,大家是不是觉得很简单,但是我在开发过程中还是遇到了问题,在本文中做一个分享。,既然要记录所有的请求,我们可以创建一个过滤器LogRequestFilter, 统一拦截所有的请求,读取里面的输入流InputStream,我想大家都能想到把,具体代码如下:,但是事情往往不是按照你预期的方向发展的, 但你按照上面的设计写好代码后,发一个post请求,却返回下面的报错:,为什么会报错呢?,原因就是输入流只能读取一次。 当我们调用getInputStream()方法获取输入流时得到的是一个InputStream对象,而实际类型是ServletInputStream,它继承于InputStream。,InputStream的read()方法内部有一个postion,标志当前流被读取到的位置,每读取一次,该标志就会移动一次,如果读到最后,read()会返回-1,表示已经读取完了。如果想要重新读取则需要调用reset()方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读了。调用reset()方法的前提是已经重写了reset()方法,当然能否reset也是有条件的,它取决于markSupported()方法是否返回true。,InputStream默认不实现reset(),并且markSupported()默认也是返回false,这一点查看InputStream源码便知:,,我们再来看看ServletInputStream,可以看到该类没有重写mark(),reset()以及markSupported()方法:,,所以InputStream默认不实现reset的相关方法,而ServletInputStream也没有重写reset的相关方法,这样就无法重复读取流,这就是我们从request对象中获取的输入流就只能读取一次的原因,最后导致再次读取流的时候报错。,那该如何解决呢?,既然ServletInputStream不支持重新读写,那么为什么不把流读出来后用容器存储起来,后面就可以多次利用了。那么问题就来了,要如何存储这个流呢?,所幸JavaEE提供了一个 HttpServletRequestWrapper类,从类名也可以知道它是一个http请求包装器,其基于装饰者模式实现了HttpServletRequest界面,部分源码如下:,,从上图中的部分源码可以看到,该类并没有真正去实现HttpServletRequest的方法,而只是在方法内又去调用HttpServletRequest的方法,所以我们可以通过继承该类并实现想要重新定义的方法以达到包装原生HttpServletRequest对象的目的。,我们可以自己定义一个类CustomHttpRequestWrapper,继承自HttpServletRequestWrapper,定义一个成员变量bodyInStringFormat,存储body中获取到的数据,其实字符串底层是字节数组,然后重写getInputStream方法,构造一个ByteArrayInputStream输入流,而ByteArrayInputStream实现了mark(),reset()以及markSupported()方法,然后让ByteArrayInputStream去读取前面保存的字符串bodyInStringFormat中的数组,从而达到重复使用的目的。,编写玩上面的代码以后,还需要再过滤器中使用,那么后续过滤器中的ServletRequest实现类都是CustomHttpRequestWrapper , 就可以再次读取body的内容了,具体代码如下:,这一下你再次向应用程序发出POST或GET请求时,就不会看到任何报错了。
文章版权声明
1 原创文章作者:cmcc,如若转载,请注明出处: https://www.52hwl.com/22626.html
2 温馨提示:软件侵权请联系469472785#qq.com(三天内删除相关链接)资源失效请留言反馈
3 下载提示:如遇蓝奏云无法访问,请修改lanzous(把s修改成x)
4 免责声明:本站为个人博客,所有软件信息均来自网络 修改版软件,加群广告提示为修改者自留,非本站信息,注意鉴别