HttpSevletRequest Body信息不能被多次读取的问题

        在 Java Web 开发中,HTTP 请求体是客户端向服务器发送数据的主要载体,例如表单提交、JSON 数据等。当服务器收到请求后,通常通过 HttpServletRequest 类获取请求体的内容。然而,HTTP 请求体通常只能被读取一次。这是因为请求体使用输入流(InputStream)进行读取,一旦读取完毕,流的位置就会到达流的末尾,无法再读取。因此,在开发中,如果不小心读取了请求体一次,后续的代码将无法访问请求体数据,从而导致数据丢失或者异常。

1. 问题背景

HTTP 请求体的读取限制

HTTP 请求体(如 POST 请求中的 JSON 数据或表单数据)是通过输入流(InputStream)传输的。Servlet 容器在接收到请求时,会将输入流读取并解析到内存中,然后进行后续的处理。但是,HTTP 请求体的流一次性读取特性意味着:

  1. 只能读取一次:一旦读取完请求体的数据,流的位置就会指向末尾,无法再次读取同一数据。
  2. 重复访问时失败:如果在请求处理过程中,多个组件或方法需要访问请求体数据,但该数据已经被读取过一次,那么后续的读取操作将无法成功,通常会抛出异常或返回空数据。

这种问题在需要多次读取请求体的场景中尤为明显。例如,在拦截器或过滤器中读取请求体数据时,后续的业务处理方法(如 Controller)可能无法再访问请求体。

2. 问题产生的场景

以下是一些常见的导致请求体只能读取一次的场景:

  • 使用 Servlet 读取请求体:当使用 HttpServletRequest 获取请求体时,流会被消耗,无法再次获取。
  • Spring MVC 中的 @RequestBody:Spring MVC 提供了 @RequestBody 注解,用于直接将请求体映射为 Java 对象。但一旦请求体被映射,流就会被消耗,导致后续无法再访问请求体数据。
  • 自定义过滤器或拦截器:在一些需要多次读取请求体的场景下,过滤器或拦截器读取请求体后,后续代码无法再次访问请求体数据。

3. 解决方案

使用 HttpServletRequestWrapper 包装请求体

通过创建一个自定义的 HttpServletRequestWrapper 来缓存请求体的数据,并提供多次读取的能力。这种方式允许请求体数据在第一次读取时被缓存,后续的请求处理流程可以从缓存中读取数据。

import cn.hutool.extra.servlet.ServletUtil;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.InputStreamReader;/***  Request Body 缓存 Wrapper** @author wingzingliu*/
public class CacheRequestBodyWrapper extends HttpServletRequestWrapper {/*** 缓存的内容*/private final byte[] body;public CacheRequestBodyWrapper(HttpServletRequest request) {super(request);body = ServletUtil.getBodyBytes(request);}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(this.getInputStream()));}@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);// 返回 ServletInputStreamreturn new ServletInputStream() {@Overridepublic int read() {return inputStream.read();}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {}@Overridepublic int available() {return body.length;}};}}
创建过滤器
import cn.hutool.core.util.StrUtil;
import org.springframework.http.MediaType;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** Request Body 缓存 Filter,实现它的可重复读取** @author wingzingliu*/
public class CacheRequestBodyFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws IOException, ServletException {filterChain.doFilter(new CacheRequestBodyWrapper(request), response);}@Overrideprotected boolean shouldNotFilter(HttpServletRequest request) {// 只处理 json 请求内容return !isJsonRequest(request);}public static boolean isJsonRequest(ServletRequest request) {return StrUtil.startWithIgnoreCase(request.getContentType(), 		MediaType.APPLICATION_JSON_VALUE);}
}
自动配置类
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.servlet.Filter;
/*** @author wingzingliu*/
@Configuration
public class WebConfiguration implements WebMvcConfigurer {/*** 创建 RequestBodyCacheFilter Bean,可重复读取请求内容*/@Beanpublic FilterRegistrationBean<CacheRequestBodyFilter> requestBodyCacheFilter() {return createFilterBean(new CacheRequestBodyFilter(), WebFilterOrderEnum.REQUEST_BODY_CACHE_FILTER);}public static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);bean.setOrder(order);return bean;}
}
/*** Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期**  考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enum 包下** @author wingzingliu*/
public interface WebFilterOrderEnum {...int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500;...}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/64468.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ARM CCA机密计算安全模型之受保护内存

安全之安全(security)博客目录导读 目录 1、一般威胁模型 2、可能的缓解措施 3、CCA 使用外部内存 4、外部内存初始化 5、资产 6、基线内存保护配置文件 7、内存清理 8、额外的内存保护 许多 Realm 和 CCA 资产存储在外部内存中。本博客讨论针对基于内存攻击的缓解措施…

ubuntu升级python版本

Ubuntu升级Python版本 解压缩文件&#xff1a; 下载完成后&#xff0c;解压缩文件&#xff1a; tar -xf Python-3.12.0.tgz编译并安装&#xff1a; 进入解压后的目录&#xff0c;然后配置和安装Python&#xff1a; codecd Python-3.12.0 ./configure --enable-optimizations ma…

从Windows到Linux:跨平台数据库备份与还原

数据库的备份与还原 目录 引言备份 2.1 备份所有数据库2.2 备份单个数据库2.3 备份多个指定数据库 传输备份文件还原 4.1 还原所有数据库4.2 还原单个数据库4.3 还原多个指定数据库 注意事项拓展 1. 引言 在不同的操作系统间进行数据库迁移时&#xff0c;命令行工具是我们的…

准备写一个内网穿透的工具

准备写一个内网穿透的工具&#xff0c;目前只实现了HTTP内网穿透的GET方式&#xff0c;看能不能坚持写下去 git地址&#xff1a; xuejiazhi/PortRelay

google guava 库 最佳实践 学习指南 学习实用示例

学习Guava库 核心提纲: 入门示例 guava 最佳实践 学习指南 1. 概览与入门 Guava库的介绍Guava的安装与依赖配置Guava的主要模块和功能概览 入门示例 2. 基本工具类 Preconditions&#xff1a;用于断言和参数检查Verify&#xff1a;用于验证对象状态 https://blog.csdn.net/…

捣鼓小玩意-分批处理工具类

分批处理工具类 博主自己的博客点击访问&#xff08;内容大部分更新在自己的博客&#xff0c;有时间才会整理到CSDN&#xff09; 有时候会遇到一些大批量数据成千上万的列表&#xff0c;如果单独一个循环处理&#xff0c;可能会很慢&#xff0c;或者是遇到如需要根据id in ()…

提示词战术技巧-前导课

在人工智能的快速发展中,大模型(如OpenAI的GPT-4和GPT-4)为各行各业带来了革命性的变化。正确地选择适合的业务需求的大模型,并掌握提示词工程的编写与调优是成功实现AI应用的关键。本文旨在提供一个全面的教学指南,帮助学习者理解和掌握大模型的业务选型与提示词工程的技…

零配置打包工具 Parcel 的详细使用指南

前言 在前端开发中&#xff0c;选择一个高效且易用的打包工具至关重要。Parcel 作为一款零配置的 Web 应用打包工具&#xff0c;凭借其卓越的性能和简单的使用体验&#xff0c;赢得了众多开发者的青睐。它不仅能够自动处理依赖关系和代码打包&#xff0c;还支持热模块替换和多…

【AI知识】逻辑回归介绍+ 做二分类任务的实例(代码可视化)

1. 分类的基本概念 在机器学习的有监督学习中&#xff0c;分类一种常见任务&#xff0c;它的目标是将输入数据分类到预定的类别中。具体来说&#xff1a; 分类任务的常见应用&#xff1a; 垃圾邮件分类&#xff1a;判断一封电子邮件是否是垃圾邮件 。 医学诊断&#xff1a;…

删除 C 盘空文件夹--递归删除

# Language: Powershell # Style: TypeScript# 文件名: # "文件(夹)&#xff1a;删除空文件夹.ps1"function Remove-EmptyDirectories {param ([string]$Path)# 获取所有目录&#xff0c;把子目录排列在父目录前面。# 删除所有子文件(夹)后&#xff0c;可判断父目录为…

为SSH2协议服务器的用户设置密钥

目录 私钥的创建1. 在服务器上直接生成2. 如果需要配置免密登录3. 查看生成的密钥 导出私钥至SSH用户获取sudo权限 新的一台服务器类型是SSH2&#xff1a;这表示服务器支持SSH&#xff08;Secure Shell&#xff09;协议的第二个版本。SSH是一个网络协议&#xff0c;用于加密方式…

level2逐笔委托查询接口

沪深逐笔委托队列查询 前置步骤 分配数据库服务器 查询模板 以下是沪深委托队列查询的请求模板&#xff1a; http://<数据库服务器>/sql?modeorder_book&code<股票代码>&offset<offset>&token<token>查询参数说明 参数名类型说明mo…

微软开源GraphRAG的使用教程(最全,非常详细)

GraphRAG的介绍 目前微软已经开源了GraphRAG的完整项目代码。对于某一些LLM的下游任务则可以使用GraphRAG去增强自己业务的RAG的表现。项目给出了两种使用方式&#xff1a; 在打包好的项目状态下运行&#xff0c;可进行尝试使用。在源码基础上运行&#xff0c;适合为了下游任…

文献研读|基于像素语义层面图像重建的AI生成图像检测

前言&#xff1a;本篇文章主要对基于重建的AI生成图像检测的四篇相关工作进行介绍&#xff0c;分别为基于像素层面重建的检测方法 DIRE 和 Aeroblade&#xff0c;以及基于语义层面重建的检测方法 SimGIR 和 Zerofake&#xff1b;并对相应方法进行比较。 相关文章&#xff1a;论…

VScode MAC按任意键关闭终端 想要访问桌面文件

说明 最近配置MAC上CPP的运行环境&#xff0c;在安装必要的CPP插件后&#xff0c;配置launch和task等json文件后&#xff0c;点击运行三角形&#xff0c;每次都会跳出main想要访问桌面上的文件。并且输出也是在调试控制台&#xff0c;非常逆天。 尝试 尝试1:尽管我尝试将ta…

7.日常算法

1. NC140 排序 题目来源 要求使用堆进行排序 class Solution { public: void adjustDown(vector<int>& arr, int root, int n){int parent root;int chiled root * 2 1;while (chiled < n){if (chiled 1 < n && arr[chiled 1] > arr[chi…

Linux Shell 脚本编程基础知识篇

ℹ️大家好&#xff0c;我是练小杰&#xff0c;从本文是Linux shell脚本编程的基础知识内容&#xff0c;后续我会不断补充~~ 更多Linux 相关内容请点击&#x1f449;“Linux专栏”~ 假面驾驭&#xff0c;时王&#xff0c;假面骑士时王~~ 文章目录 什么是 Linux Shell主要功能…

QT绘制同心扇形

void ChartForm::paintEvent(QPaintEvent *) {QPainter painter(this);painter.setRenderHint(QPainter::Antialiasing);// 设置抗锯齿painter.save();// 设置无边框&#xff08;不需要设置QPen&#xff0c;因为默认是不绘制边框的&#xff09;QPen pen(Qt::NoPen);// QPen pen…

【0369】Postgres内核 checkpoint record 与 expectedTLEs 校验 ( 12 )

上一篇: 文章目录 1. checkpoint record 校验1.1 预期 timeline1.2 timeline 判断1.3 checkPoint ThisTimeLineID2. 最小 recovery point3. 初始化 LastRec1. checkpoint record 校验 如果 checkpoint record 的位置不在请求 timeline 历史中的预期 timeline 上,则无法继续…

TL3568/TL3562更改主机名,在Kernel用menuconfig失效

前言 最近在玩RK3562开发板&#xff0c;想改串口调试时看到的主机名&#xff0c;开发板的主机名默认是RK3562-Tronlong&#xff0c;如图&#xff1a; 按照之前玩T113开发版&#xff0c;在Kernel通过make menuconfig&#xff0c;可以改。但是在这个RK3562&#xff0c;改了后&…