当前位置: 首页 > 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/611111/

相关文章:

  • 免费数字人形象哪里找?lite-avatar形象库150+资源实测
  • Z-Image-Turbo-辉夜巫女高性能部署:Xinference量化加载+Gradio并发优化实测
  • 科研助手实战:OpenClaw+Phi-3-vision自动整理文献图表数据
  • **为生命按下“刷新键”:当细胞科技成为健康管理的新日常**
  • 深度学习项目训练环境快速上手指南:5分钟激活dl环境、解压数据、启动训练
  • 原子操作的内存顺序
  • 解码AMD EPYC CPU命名规则:从数字到性能的全面解析
  • [5个高效方案]的开源项目X批量授权激活完全指南
  • 【PyCon 2025闭门分享精要】:Python 3.14 JIT底层调度器深度调优——用3行代码撬动47% CPU利用率提升
  • cv_unet_image-colorization实战案例:退役军人事务局荣誉影像AI修复工程
  • 考完金山KOS多久出成绩?在哪查?一篇说清!
  • 2026届必备的五大降AI率平台推荐
  • 从沙漏到矿机:聊聊离散元法DEM是怎么‘算’出颗粒世界的(附Rocky/EDEM软件对比与学习资源)
  • OpenClaw日志分析:千问3.5-35B-A3B-FP8任务执行效率优化
  • 最长有效括号力扣--32
  • MIT AI工具一分钟预览高度逼真3D打印成品外观,所见即所得
  • 2026年热门的泸州塑料设备焊接服务/塑料设备焊接/泸州塑料设备焊接/塑料设备焊接加工公司对比推荐 - 行业平台推荐
  • 智慧农业草莓成熟度识别 基于cnn的YOLOv11深度学习 智慧农业草莓成熟度目标检测系统 草莓识别系统(数据集使用 YOLOv11 进行草莓成熟度计数与检测 注意:此模块是在以下资源的+模型+界面)
  • 2026年热门的玻璃钢防腐/泸州玻璃钢防腐施工/玻璃钢防腐工程主流厂家对比评测 - 行业平台推荐
  • OpenClaw版本升级:Qwen2.5-VL-7B兼容性测试与迁移指南
  • IDEA 2023配置Resin启动泛微Ecology 9项目,解决高版本不支持问题
  • Claude Code 权限 / 安全审查调用流程图
  • OpenClaw故障排查大全:千问3.5-27B接口连接7类错误解决
  • 2026年4月叉装车出租正规机构推荐,行业内叉装车出租正规公司有哪些君顺联合满足多元需求 - 品牌推荐师
  • SolidWorks 2019 + Fusion 360:手把手教你搞定复杂机械臂模型的URDF导出(附开源模型)
  • Shell脚本中的算术运算:let、(())、expr三种方式全解析(附避坑指南)
  • 避坑指南:MediaPipe安装常见报错解决方案(附虚拟环境配置技巧)
  • OpenClaw+千问3.5-9B社交媒体管理:定时发布与智能互动
  • Element给所有弹窗组件增加属性
  • VisionMaster实战:高效提取图像中的几何与文字信息