HttpServletRequest & HttpServletResponse 中 Body 的获取
获取 HttpServletRequest 中的请求体
HttpServletRequest#getInputStream() 获取到请求的输入流,从该输入流中可以读取到请求体。不过这个流在被我们的代码 read 过后,之后的代码就会报错,因为流已经被我们读取过了 , 尝试使用 mark() , reset() 也是不行的,会抛出异常。可以通过将 HttpServletRequest 对象包装一层的方式来实现这个功能。
package org.hepeng.commons.http; import lombok.AllArgsConstructor; import lombok.Data; import org.apache.commons.io.IOUtils; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; /** * * @author he peng * @date 2018/9/11 */ public class BodyCachingHttpServletRequestWrapper extends HttpServletRequestWrapper { private byte[] body; private ServletInputStreamWrapper inputStreamWrapper; public BodyCachingHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); this.body = IOUtils.toByteArray(request.getInputStream()); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.body); this.inputStreamWrapper = new ServletInputStreamWrapper(byteArrayInputStream); resetInputStream(); } private void resetInputStream() { this.inputStreamWrapper.setInputStream(new ByteArrayInputStream(this.body != null ? this.body : new byte[0])); } public byte[] getBody() { return body; } @Override public ServletInputStream getInputStream() throws IOException { return this.inputStreamWrapper; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.inputStreamWrapper)); } @Data @AllArgsConstructor private static class ServletInputStreamWrapper extends ServletInputStream { private InputStream inputStream; @Override public boolean isFinished() { return true; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return this.inputStream.read(); } } }
获取 HttpServletResponse 中的响应体
通过使用 ByteArrayOutputStream 将原 HttpSevletResponse 进行一层包装就可以实现。ByteArrayOutputStream 是将数据写入到它内部的缓冲区中,这样我们就可以获取到这个数据了。
package org.hepeng.commons.http; import lombok.AllArgsConstructor; import lombok.Data; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; /** * @author he peng * @date 2018/10/1 */ public class BodyCachingHttpServletResponseWrapper extends HttpServletResponseWrapper { private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); private HttpServletResponse response; public BodyCachingHttpServletResponseWrapper(HttpServletResponse response) { super(response); this.response = response; } public byte[] getBody() { return byteArrayOutputStream.toByteArray(); } @Override public ServletOutputStream getOutputStream() { return new ServletOutputStreamWrapper(this.byteArrayOutputStream , this.response); } @Override public PrintWriter getWriter() throws IOException { return new PrintWriter(new OutputStreamWriter(this.byteArrayOutputStream , this.response.getCharacterEncoding())); } @Data @AllArgsConstructor private static class ServletOutputStreamWrapper extends ServletOutputStream { private ByteArrayOutputStream outputStream; private HttpServletResponse response; @Override public boolean isReady() { return true; } @Override public void setWriteListener(WriteListener listener) { } @Override public void write(int b) throws IOException { this.outputStream.write(b); } @Override public void flush() throws IOException { if (! this.response.isCommitted()) { byte[] body = this.outputStream.toByteArray(); ServletOutputStream outputStream = this.response.getOutputStream(); outputStream.write(body); outputStream.flush(); } } } }
flush() 函数是必须提供的 ,否则流中的数据无法响应到客户端 , ByteArrayOutputStream 没有实现 flush() 。像 SpringMVC 这类框架会去调用这个响应输出流中的 flush() 函数 ,而且有可能在出现多次调用的情况,多次调用会产生问题使得客户端得到错误的数据,比如这样的 :
{"errorCode":30001,"errorMsg":"用户未认证","token":null,"entity":null}{"errorCode":30001,"errorMsg":"用户未认证","token":null,"entity":null} ,出现这种情况就说明 flush() 被调用了两次。所以需要在这里判断一下 HttpServletResponse#isCommitted() 。
获取请求体、相应体的包装类在 Filter 中的使用
package org.hepeng.commons.http.filter; import com.tepin.commons.http.BodyCachingHttpServletRequestWrapper; import com.tepin.commons.http.BodyCachingHttpServletResponseWrapper; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author he peng * @date 2018/10/2 */ public class DemoFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { BodyCachingHttpServletRequestWrapper requestWrapper = new BodyCachingHttpServletRequestWrapper((HttpServletRequest) request); byte[] requestBody = requestWrapper.getBody(); // TODO do something BodyCachingHttpServletResponseWrapper responseWrapper = new BodyCachingHttpServletResponseWrapper((HttpServletResponse) response); chain.doFilter(requestWrapper , responseWrapper); byte[] responseBody = responseWrapper.getBody(); // TODO do something } @Override public void destroy() { } }
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
并发编程之美-深入理解java多线程原理
1.什么是多线程? 多线程是为了使得多个线程并行的工作以完成多项任务,以提高系统的效率。线程是在同一时间需要完成多项任务的时候被实现的。 2.了解多线程 了解多线程之前我们先搞清楚几个重要的概念! 如上图所示:对我们的项目有一个主内存,这个主内存里面存放了我们的共享变量、方法区、堆中的对象等。 3.线程的工作过程 每当我们开启一个线程的时候,线程会为我们开辟一块工作内存,将主内存中的共享变量复制一个副本存入工作内存中,并协调方法区生成栈针,以及对堆的引用(指针)。 如果在执行过程中线程对工作内存中的共享变量进行的修改操作,此时会向主内存回写我们修改的变量。 4.多线程带来的问题 我们模拟这样一个场景: 有十个用户同时购票,但是系统中只剩下了8张票,当每个用户同时开启自己的线程,将主内存中8张票复制到工作内存中,在方法中,会判断票数是否满足要求,此时,十个线程都判断满足,都要对票数进行操作。 当用户一操作后,票数=8-1=7,将数据回写至主内存。 用户二操作后,用户二的本地内存中票数为8,则修改后票数=8-1=7,继续回写至主内存, 以此下去,在我们假设十个用户同时开启线程的情况下最后主...
- 下一篇
Zuul源码阅读
Motivation 说一下,为什么要阅读Netflix Zuul,最近在看alibaba/Sentinel 在网关方面的应用。发现网关的设计模式有很多通用的地方。Netflix Zuul 对比Zuul2 更能直接的看出网关的主要功能和核心设计,易于上手,所以选择这个。同时也再次实践一下怎么高效阅读源代码。 Design and Function Zuul 的主要功能,包括,鉴权,路由,流量监控,负载,实时响应(动态配置和开关),错误处理。 这张图就是根据这些功能提出的设计,通过Filter 将整个过程连接起来, 可扩展性: 在不同阶段做不同的处理,做隔离。 通过groovy 脚本加载来实现动态加载新的filter。 ZuulFilter 下面我们就来看总重要的 ZuulFilter 的设计吧,首先是类结构图 先看最基本的IZuulFilter 接口, 定义了两个方法,这个filter 要不要执行,怎么执行。 public interface IZuulFilter { /** * a "true" return from this method means that the run(...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS关闭SELinux安全模块
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Linux系统CentOS6、CentOS7手动修改IP地址
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- Red5直播服务器,属于Java语言的直播服务器