当前位置: 首页 > news >正文

DotNetPy:现代.NET 与 Python 互操作 实战指南延

我为什么会发出这个疑问呢?是因为我研究Web开发中的一个问题时,HTTP请求体在 Filter(过滤器)处被读取了之后,在 Controller(控制层)就读不到值了,使用 @RequestBody 的时候。

无论是字节流(InputStream / OutputStream)还是字符流(Reader / Writer),所有基于流的读取操作都会维护一个 "位置指针"。

初始状态下,指针指向流的起始位置(position = 0);

每次调用 read() / read(byte[]) / read(char[]) 等读取方法时,指针会向后移动对应字节数;

当指针移动到流的末尾(没有更多数据),read() 方法会返回 -1,表示流读取完毕;

指针移动后不会自动回退,也无法反向移动(除非流显式支持重置),因此再次读取只能得到 -1。

类比:IO 流的读取过程,就像用 磁带播放器听磁带 —— 磁头(对应流的位置指针)从磁带开头(指针 0)开始移动,每读一个字节 / 字符,磁头就往后走一步;当磁头走到磁带末尾,再继续播放(读取)就只能听到 "沙沙声"(流返回 -1),并且磁头不会自动回到开头。

当然,不是所有流都只能读一次,基于内存的流(如 ByteArrayInputStream / CharArrayReader)支持重置指针,因为它们的数据源是内存中的数组(数据不会消失),可以通过 mark() 和 reset() 方法将指针 恢复 到标记位置。

需要注意:

调用 reset() 前必须先调用 mark(int readlimit);

不是所有流都支持 mark() / reset(),可以通过 inputStream.markSupported() 来进行判断。

使用 mark() 和 reset() 方法:

// 仅适用于支持mark的流

public void processWithMark(InputStream input) throws IOException {

if (!input.markSupported()) {

throw new IOException("Mark not supported");

}

// 标记当前位置,参数100表示最多可回退100字节

input.mark(100);

// 第一次读取

byte[] firstRead = new byte[50];

input.read(firstRead);

System.out.println("First read: " + new String(firstRead));

// 重置到标记位置

input.reset();

// 第二次读取(相同内容)

byte[] secondRead = new byte[50];

input.read(secondRead);

System.out.println("Second read: " + new String(secondRead));

}

使用 包装类 解决上文我们提到的 HTTP请求体多次读取 的问题:

public class MyRequestWrapper extends HttpServletRequestWrapper {

private final byte[] body; // 缓存请求体的字节数组

public MyRequestWrapper(HttpServletRequest request) throws IOException {

super(request);

// 关键步骤:在构造时一次性读取并存储原始请求流

body = StreamUtils.copyToByteArray(request.getInputStream());

}

// 提供一个便捷方法,用于在过滤器中获取请求体内容(例如记录日志)

// 使用时,直接调用 getBodyString() 即可

public String getBodyString() throws UnsupportedEncodingException {

return new String(body, this.getCharacterEncoding());

}

@Override

public ServletInputStream getInputStream() throws IOException {

// 每次调用都返回一个基于缓存数据的新流

ByteArrayInputStream bais = new ByteArrayInputStream(body);

return new ServletInputStream() {

@Override

public int read() {

return bais.read();

}

@Override

public boolean isFinished() {

return bais.available() == 0;

}

@Override

public boolean isReady() {

return true;

}

@Override

public void setReadListener(ReadListener readListener) {

// 无需实现

}

};

}

@Override

public BufferedReader getReader() throws IOException {

return new BufferedReader(new InputStreamReader(this.getInputStream(), this.getCharacterEncoding()));

}

}

然后在 过滤器 处包装请求:

@Slf4j

@Configuration

public class RequestCachingFilterConfig {

@Bean

public FilterRegistrationBean requestCachingFilter() {

FilterRegistrationBean registrationBean = new FilterRegistrationBean();

// 核心:创建过滤器,包装请求为 ContentCachingRequestWrapper

registrationBean.setFilter(new OncePerRequestFilter() {

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)

throws ServletException, IOException {

// 1. 仅包装 HTTP 请求(排除 WebSocket 等)

if (request instanceof HttpServletRequest && !(request instanceof ContentCachingRequestWrapper)) {

log.info("==========进入requestCachingFilter========");

// 2. 包装请求(自动缓存请求体)

MyRequestWrapper wrappedRequest = new MyRequestWrapper(request);

filterChain.doFilter(wrappedRequest, response); // 传递包装后的请求

} else {

filterChain.doFilter(request, response); // 无需包装,直接放行

}

}

});

// 3. 配置拦截所有请求(可根据需求调整 URL 模式)

registrationBean.addUrlPatterns("/*");

registrationBean.setOrder(1); // 优先级最高,确保先于其他过滤器执行

registrationBean.setName("requestCachingFilter");

return registrationBean;

}

}

IO 流只能读取一次,是 精心设计的,贴合操作系统文件 / 网络 IO 的 "顺序消费" 特性,保持和底层系统的一致性。炙狈镣怀

http://www.jsqmd.com/news/614978/

相关文章:

  • 突破限制:开源工具实现Cursor全功能访问的完整指南
  • ​有机溶剂回收设备厂家实测
  • 20254203 2025-2026-2 《Python程序设计》实验二报告
  • 边缘计算与AI推理:在终端设备上部署模型的挑战
  • 环境变量-代理/PowerShell乱码
  • Google 迎来「DeepSeek 时刻」:TurboQuant算法实现bit无损、×加速、×压缩、零预处理揪
  • 低代码农业管理系统上线提速83%:PHP可视化配置引擎核心设计逻辑与部署 checklist
  • 安全设备-NIDS入侵检测系统
  • 你的终端神器之Oh My Zsh痰
  • 使用Alpine配置WSL ssh门户段
  • 手机扩大屏定制厂家:菲涅尔高清模压技术护航跨境出海
  • 【知网普刊】硕士独作零门槛,不收版面费,审稿快、易录用,论文发表无忧服务助力快速录用!
  • Opus4.6写小说都这么猛!直接把Mythos写成主角!
  • Linux权限详解:从入门到掌握
  • Cocos Creator 业务与原生通信详解
  • 【技术解密】fastMRI核心技术:从原始数据到临床图像的完整链路
  • 2026电镀污泥尾气治理厂家如何应对技术升级与环保合规挑战?
  • 突破性Elsevier审稿状态追踪解决方案:自动化监控系统提升学术出版效率
  • 南京道尔斯特机架式PDU重新定义工业级安全电源管理新范式
  • IntelliJ IDEA社区版下载安装2026新(附安装包)
  • 喔去,litellm 竟然被投毒了,赶紧检查你的机器中招了没有送
  • NVIDIA Profile Inspector:深度优化显卡性能的游戏技术解决方案
  • HTML函数在老旧浏览器运行慢是硬件问题吗_软硬协同分析【教程】
  • 2026届学术党必备的五大降重复率助手推荐榜单
  • Fan Control风扇控制软件:从入门到精通的散热优化方案
  • Common Voice 语音数据集:从技术探索到实战应用的完整指南
  • 程序员副业变现全攻略:从技术到收入
  • 【高并发架构紧急升级】:Java 25虚拟线程已默认启用?3天内完成Tomcat/Netty/Reactor适配的实操手册
  • 小白友好:Local SDXL-Turbo极简使用教程,开箱即用无需复杂配置
  • linux——信号量