- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
在基于Spring开发Java项目时,可能需要重复读取HTTP请求体中的数据,例如使用拦截器打印入参信息等,但当我们重复调用getInputStream()或者getReader()时,通常会遇到类似以下的错误信息: 大体的意思是当前request的getInputStream()已经被调用过了。那为什么会出现这个问题呢?
主要原因有两个,一是Java自身的设计中,InputStream作为数据管道本身只支持读取一次,如果要支持重复读取的话就需要重新初始化;二是Servlet容器中Request的实现问题,我们以默认的Tomcat为例,可以发现在Request有两个boolean类型的属性,分别是usingReader和usingInputStream,当调用getInputStream()或getReader()时会分别检查两个属性的值,并在执行后将对应的属性设置为true,如果在检查时变量的值已经为true了,那么就会报出以上错误信息.
涉及到变量的修改,我们首先想到的就是有没有提供方法进行修改,不过可惜的是usingReader和usingInputStream并未提供,所以想要在使用过程中修改这两个属性估计只能靠反射了,在使用过程中每次调用后通过反射将usingReader和usingInputStream设置为false,每次根据读取出的内容把数据流初始化回去,理论上就可以再次读取了.
首先说反射机制本身就是通过破坏类的封装来实现动态修改的,有点过于粗暴了,其次也是主要原因,我们只能针对我们自己实现的代码进行处理,框架本身如果调用getInputStream()和getReader()的话,我们就没法通过这个办法干预了,所以这个方案在给予Spring的Web项目中并不可行.
HttpServletRequest是一个接口,理论上我们只需要创建一个实现类就可以自定义getInputStream()和getReader()的行为,自然也就能解决RequestBody不能重复读取的问题,但这个方案的问题在于HttpServletRequest有70个方法,而我们只需要修改其中两个而已,通过这种方式去解决有点得不偿失.
Spring本身提供了一个Request包装类来处理重复读取的问题,即ContentCachingRequestWrapper,其实现思路就是在读取RequestBody时将内存缓存到它内部的一个字节流中,后续读取可以通过调用getContentAsString()或getContentAsByteArray()获取到缓存下来的内容.
之所以说这个方案是部分场景可行主要是两个方面,一是ContentCachingRequestWrapper没有重写getInputStream()和getReader()方法,所以框架中使用这两个方法的地方依然获取不到缓存下来的内容,仅支持自定义的业务逻辑;第二点和第一点有所关联,因为其没有修改getInputStream()和getReader()方法,所以我们在使用时只能在使用RequestBody注解后使用ContentCachingRequestWrapper,否则就会出现RequestBody注解修饰的参数无法正常读取请求体的问题,也就限定了它的使用范围如下图所示:
如果仅需要在业务代码后再次读取请求体内容,那么使用ContentCachingRequestWrapper也足以满足需求,具体使用方法请参考下一节的说明.
之前我们提到实现HttpServletRequest需要实现70个方法,所以不太可能自行实现,这个方案算是进阶版本,继承HttpServletRequest的实现类,之后再自定义我们需要修改的两个方法.
HttpServletRequest作为一个接口,肯定会有其实现去支撑它的业务功能,因为Servlet容器的选择较多,我们也不能使用某一方提供的实现,所以选择的范围也就被限制到了Java EE(现在叫Jakarta EE)标准范围内,通过查看HttpServletRequest的实现,可以发现在标准内提供了一个包装类:HttpServletRequestWrapper,我们的方案也是围绕它展开.
// 1.继承HttpServletRequestWrapper
public class CustomRequestWrapper extends HttpServletRequestWrapper {
// 2.定义final属性,用于缓存请求体内容
private final byte[] content;
public CustomRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
// 3.构造方法中将请求体内容缓存到内部属性中
this.content = StreamUtils.copyToByteArray(request.getInputStream());
}
// 4.重新getInputStream()
@Override
public ServletInputStream getInputStream() {
// 5.将缓存下来的内容转换为字节流
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(content);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() {
// 6.读取时读取第5步初始化的字节流
return byteArrayInputStream.read();
}
};
}
// 7.重写getReader()方法,这里复用getInputStream()的逻辑
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
// 1.实现Filter接口,此处也可以选择继承HttpFilter
public class RequestWrapperFilter implements Filter {
// 2. 重写或实现doFilter方法
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 3.此处判断是为了缩小影响范围,本身CustomRequestWrapper只是针对HttpServletRequest,不进行判断可能会影响其他类型的请求
if (request instanceof HttpServletRequest) {
// 4.将默认的HttpServletRequest转换为自定义的CustomRequestWrapper
CustomRequestWrapper requestWrapper = new CustomRequestWrapper((HttpServletRequest) request);
// 5.将转换后的request传递至调用链中
chain.doFilter(requestWrapper, response);
} else {
chain.doFilter(request, response);
}
}
}
/**
* 过滤器配置,支持第三方过滤器
*/
@Configuration
public class FilterConfigure {
/**
* 请求体封装
* @return
*/
@Bean
public FilterRegistrationBean<RequestWrapperFilter> filterRegistrationBean(){
FilterRegistrationBean<RequestWrapperFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new RequestWrapperFilter());
bean.addUrlPatterns("/*");
return bean;
}
}
至此我们就可以在项目中重复读取请求体了,如果选择使用Spring提供的ContentCachingRequestWrapper,那么在Filter中将CustomRequestWrapper替换为ContentCachingRequestWrapper即可,不过需要注意在上一节提到的可用范围较小的问题.
文章内的代码可以参考 https://gitee.com/itartisans/itartisans-framework,这是我开源的一个SpringBoot项目脚手架,我会不定期加入一些通用功能,欢迎关注.
最后此篇关于SpringBoot项目中HTTP请求体只能读一次?试试这方案的文章就讲到这里了,如果你想了解更多关于SpringBoot项目中HTTP请求体只能读一次?试试这方案的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
据我了解,HTTP POST 请求的正文大小没有限制。因此,客户端可能会在一个 HTTP 请求中发送 千兆字节 的数据。现在我想知道 HTTP 服务器应该如何处理此类请求。 Tomcat 和 Jett
在了解Web Deploy我遇到了一些讨论 http://+:80 和 http://*:80 的 netsh.exe 命令。这些是什么意思? 最佳答案 引自URLPrefix Strings (Wi
假设我有一个负载均衡器,然后是 2 个 Web 服务器,然后是一个负载均衡器,然后是 4 个应用程序服务器。 HTTP 响应是否遵循与 HTTP 请求服务相同的路径? 最佳答案 按路径,我假设您是网络
我有一个带有 uri /api/books/122 的资源,如果在客户端为此资源发送 HTTP Delete 时该资源不存在,那么相应的响应代码是什么这个 Action ?是不是404 Not Fou
是否有特定的(或约定的)HTTP 响应消息(或除断开连接之外的其他操作)来阐明服务器不接受 pipelined HTTP requests ? 我正在寻找能让客户端停止流水线化它的请求并分别发送每个请
在了解Web Deploy我遇到了一些讨论 http://+:80 和 http://*:80 的 netsh.exe 命令。这些是什么意思? 最佳答案 引自URLPrefix Strings (Wi
我有一个带有 uri /api/books/122 的资源,如果在客户端为此资源发送 HTTP Delete 时该资源不存在,那么相应的响应代码是什么这个 Action ?是不是404 Not Fou
关闭。这个问题需要更多focused .它目前不接受答案。 想改进这个问题吗? 更新问题,使其只关注一个问题 editing this post . 关闭 8 年前。 Improve this qu
我使用 Mule 作为 REST API AMQP。我必须发送自定义请求方法:“PRINT”,但我收到: Status Code: 400 Bad Request The request could
我需要针对具有不同 HTTP 响应代码的 URL 测试我的脚本。我如何获取响应代码 300、303 或 307 等的示例/示例现有 URL? 谢谢! 最佳答案 您可以使用 httpbin为此目的。 例
我正在尝试编写一个程序来匹配 HTTP 请求及其相应的响应。似乎在大多数情况下一切都运行良好(当传输完全有序时,即使不是,通过使用 TCP 序列号)。 我发现的唯一问题是当我有流水线请求时。在那之后,
RESTful Web Services鼓励使用 HTTP 303将客户端重定向到资源的规范表示。它仅在 HTTP GET 的上下文中讨论主题。 这是否也适用于其他 HTTP 方法?如果客户端尝试对非
当使用chunked HTTP传输编码时,为什么服务器需要同时写出chunk的字节大小并且后续的chunk数据以CRLF结尾? 这不会使发送二进制数据“CRLF-unclean”和方法有点多余吗? 如
这个问题在这里已经有了答案: Is it acceptable for a server to send a HTTP response before the entire request has
如果我向同一台服务器发出多个 HTTP Get 请求并收到每个请求的 HTTP 200 OK 响应,我如何使用 Wireshark 判断哪个请求映射到哪个响应? 目前看起来像是发出了一个 http 请
func main() { http.HandleFunc("/", handler) } func handler(w http.ResponseWriter, r http.Request
我找不到有值(value)的 NodeJS with Typescript 教程,所以我在无指导下潜入水中,果然我有一个问题。 我不明白这两行之间的区别: import * as http from
问一个关于Are HTTP headers case-sensitive?的问题,如果 HTTP 方法区分大小写,大多数服务器如何处理“get”或“post”与“GET”或“POST”? 例如,看起来
我正在使用ASP.NET,在其中我通过动词GET接收查询,该应用程序专用于该URL。 该代码有效,但是如果用户发送的密码使http 200无效,请回答我,并在消息的正文中显示“Fail user or
Closed. This question needs details or clarity。它当前不接受答案。 想改善这个问题吗?添加详细信息,并通过editing this post阐明问题。 9
我是一名优秀的程序员,十分优秀!