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

装饰器模式--RequestWrapper、请求流request无法被重复读取

目录

  • 前言
  • 一、场景
  • 二、原因分析
  • 三、解决
  • 四、更多

前言

在这里插入图片描述
曾经遇见这么一段代码,能看出来是把request又重新包装了一下,核心信息都不会改变

后面了解到这叫

装饰器模式(Decorator Pattern) :也称为包装模式(Wrapper Pattern) 是指在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。
装饰器模式的核心是功能扩展,使用装饰器模式可以透明且动态地扩展类的功能。

概念是这样,但还是不懂,好好的,你装饰它干啥?

一、场景

在这里插入图片描述
最常见的场景:在进入到控制器之前,请求httpRequest在过滤器或拦截器中被处理

这些处理可能是: 读取请求体RequestBody中的某个属性值;

	 @Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// ....code = ServletUtils.getRequestBodyValue(request,"code");// ...}public static String getRequestBodyValue(HttpServletRequest request,String key) throws IOException, JSONException {if (key == null || key.isEmpty()) {throw new IllegalArgumentException("Key cannot be null or empty.");}// 读取请求体BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));String line;while ((line = reader.readLine()) != null) {requestBody.append(line);}// 解析JSON字符串JSONObject jsonObject = JSON.parseObject(requestBody.toString());// 获取参数值String value = jsonObject.getString(key);return value;}

当我这么做的时候,发现在请求进入到Controller后报错了。

运行报错HttpMessageNotReadableException: Required request body is missing:
在这里插入图片描述

二、原因分析

过滤器中的 request.getInputStream读取了请求流的信息,后续的过滤器,或者Controller(@RequestBody)都将得到一个“失效”的Request对象

展开讲讲:

在Java的HttpServletRequest中,请求体(Request Body)是以流的形式提供的,通常通过getInputStream()或getReader()方法访问。这些方法只能被调用一次

(为啥呢?)

请求体是一个输入流(ServletInputStream),流的内容是按顺序读取的。
流一旦被读取后,指针会移动到流的末尾,后续再次读取时将无法重新获取内容
其次,HTTP协议本身设计为单次传输,请求体流的内容在传输完成后不会自动重置。

gan,没看懂,反正就是只能读一次呗

三、解决

出来吧,装饰器–!

核心:继承HttpServletRequestWrapper,应用了装饰器模式对HttpServletRequest进行了增强。
具体表现为:缓存请求体,并且重写getInputStream和getReader方法。

一句话: 后续读取的是缓存的请求体而不是原始流

package com.hong.security.common;import org.springframework.util.StreamUtils;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;/*** @author wanghong* @date 2020/05/11 22:47**/
public class WrappedRequest extends HttpServletRequestWrapper {private byte[] requestBody = null;public WrappedRequest(HttpServletRequest request) {super(request);// 缓存请求bodytry {requestBody = StreamUtils.copyToByteArray(request.getInputStream());} catch (IOException e) {e.printStackTrace();}}@Overridepublic ServletInputStream getInputStream() throws IOException {if (requestBody == null) {requestBody = new byte[0];}final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);return new ServletInputStream() {@Overridepublic int read() throws IOException {return bais.read();}@Overridepublic boolean isFinished() {// TODO Auto-generated method stubreturn true;}@Overridepublic boolean isReady() {// TODO Auto-generated method stubreturn true;}@Overridepublic void setReadListener(ReadListener listener) {// TODO Auto-generated method stub}};}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(getInputStream()));}
}

如果在构造MyRequestBodyWrapper之前已经有其他组件读取了请求体,则会导致缓存失败。因此,确保这个包装器是在请求体首次读取之前应用的非常重要

import com.tty.tty_admin.common.entity.MyRequestBodyWrapper;
import org.springframework.stereotype.Component;
import org.springframework.util.ServletUtils;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 java.io.IOException;@Component  // 必须注册到容器中才能生效
public class MyTestFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("我的测试");HttpServletRequest request = (HttpServletRequest) servletRequest;MyRequestBodyWrapper wrappedRequest = new MyRequestBodyWrapper(request);// 获取请求体内容(可选)String code = ServletUtils.getRequestBodyValue(request, "code");System.out.println(code);// 传递装饰后的请求对象filterChain.doFilter(wrappedRequest, servletResponse);}@Overridepublic void destroy() {Filter.super.destroy();}

filterChain.doFilter(wrappedRequest, servletResponse); 这样就能保证传入控制器的是requestwrapper,而不是原request!!!!

四、更多

学习若依框架发现,利用Spring Cloud Gateway内置的ServerWebExchangeUtils.cacheRequestBodyAndRequest方法,减少了手动实现请求包装器的复杂性

CacheRequestFilter,优秀优秀!

package com.ruoyi.gateway.filter;import java.util.Collections;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** 解决流不能重复读取问题* * @author ruoyi*/
@Component
public class CacheRequestFilter extends AbstractGatewayFilterFactory<CacheRequestFilter.Config>
{public CacheRequestFilter(){super(Config.class);}@Overridepublic String name(){return "CacheRequestFilter";}@Overridepublic GatewayFilter apply(Config config){CacheRequestGatewayFilter cacheRequestGatewayFilter = new CacheRequestGatewayFilter();Integer order = config.getOrder();if (order == null){return cacheRequestGatewayFilter;}return new OrderedGatewayFilter(cacheRequestGatewayFilter, order);}public static class CacheRequestGatewayFilter implements GatewayFilter{@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){// GET DELETE 不过滤,get请求没有请求体,delete请求一般也不会用到HttpMethod method = exchange.getRequest().getMethod();if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE){return chain.filter(exchange);}return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> {if (serverHttpRequest == exchange.getRequest()){return chain.filter(exchange);}return chain.filter(exchange.mutate().request(serverHttpRequest).build());});}}@Overridepublic List<String> shortcutFieldOrder(){return Collections.singletonList("order");}static class Config{private Integer order;public Integer getOrder(){return order;}public void setOrder(Integer order){this.order = order;}}
}

重点,这一段

return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> {if (serverHttpRequest == exchange.getRequest()){return chain.filter(exchange);}return chain.filter(exchange.mutate().request(serverHttpRequest).build());});

使用装饰器后,控制器中会自动使用装饰后的ServerWebExchange中的request对象,这是因为Spring Cloud Gateway在处理请求时会通过过滤器链来修改和增强ServerWebExchange。具体来说,CacheRequestFilter通过ServerWebExchangeUtils.cacheRequestBodyAndRequest方法缓存了请求体,并替换了原始的ServerWebExchange中的请求对象。

相关文章:

装饰器模式--RequestWrapper、请求流request无法被重复读取

目录 前言一、场景二、原因分析三、解决四、更多 前言 曾经遇见这么一段代码&#xff0c;能看出来是把request又重新包装了一下&#xff0c;核心信息都不会改变 后面了解到这叫 装饰器模式&#xff08;Decorator Pattern&#xff09; &#xff1a;也称为包装模式(Wrapper Pat…...

【算法题】小鱼的航程

问题&#xff1a; 分析 分析题目&#xff0c;可以看出&#xff0c;给你一个开始的星期&#xff0c;再给一个总共天数&#xff0c;在这些天内&#xff0c;只有周六周日休息&#xff0c;其他全要游泳250公里。 那分支处理好啦 当星期为6时&#xff0c;需要消耗2天&#xff0c;…...

视频录像机视频通道是指什么

视频录像机的视频通道是指摄像机在监控矩阵或硬盘录像机设备上的视频输入的物理位置。 与摄像头数量关系&#xff1a;在视频监控系统中&#xff0c;有多少个摄像头就需要多少路视频通道&#xff0c;通道数量决定了视频录像机可接入摄像头的数量&#xff0c;一般硬盘录像机有4路…...

RISC-V医疗芯片工程师复合型转型的路径与策略

从RISC-V到医疗芯片:工程师复合型转型的路径与策略 一、引言 1.1 研究背景 在科技快速发展的当下,芯片技术已然成为推动各行业进步的核心驱动力之一。其中,RISC-V 架构作为芯片领域的新兴力量,正以其独特的优势迅速崛起,对整个芯片产业的格局产生着深远影响。RISC-V 架…...

《Gradio : AI awesome-demos》

《Gradio : AI awesome-demos》 This is a list of some wonderful demos & applications built with Gradio. Heres how to contribute yours! &#x1f58a;️ Natural language processing Demo name (link to demo)input type(s)output type(s)status badgeruDALL-ET…...

DeepSeek R1-7B 医疗大模型微调实战全流程分析(全码版)

DeepSeek R1-7B 医疗大模型微调实战全流程指南 目录 环境配置与硬件优化医疗数据工程微调策略详解训练监控与评估模型部署与安全持续优化与迭代多模态扩展伦理与合规体系故障排除与调试行业应用案例进阶调优技巧版本管理与迭代法律风险规避成本控制方案文档与知识传承1. 环境配…...

javascript实现生肖查询

今年是农历乙巳年&#xff0c;蛇年&#xff0c;今天突发奇想&#xff0c;想知道公元0年是农历什么年&#xff0c;生肖是什么。没想到AI给我的答复是&#xff0c;没有公元0年。我瞬间呆愣&#xff0c;怎么可能&#xff1f;后来详细查询了一下&#xff0c;还真是没有。具体解释如…...

UE5从入门到精通之如何创建自定义插件

前言 Unreal 的Plugins插件系统中有很多的插件供大家使用,包括官方的和第三方的,这些插件不仅能帮我我们实现特定功能,还能够提升我们的工作效率。 所以我们今天就来自己创建一个自定义插件,如果我们想实现什么特定的功能,我们也可以发布到商店供大家使用了。 创建插件 …...

《今日AI-人工智能-编程日报》

一、AI行业动态 AI模型作弊行为引发担忧 最新研究表明&#xff0c;AI在国际象棋对弈中表现出作弊倾向&#xff0c;尤其是高级推理模型如OpenAI的o1-preview和DeepSeek的R1模型。这些模型通过篡改代码、窃取棋路等手段试图扭转战局&#xff0c;且作弊行为与其智能水平正相关。研…...

sanitizer和valgrind

sanitizer sanitizer使用 Valgrind valgrind使用 使用上sanitizer加编译选项即可 发现sanitizer程序要正常退出才能给出分析&#xff0c;ctrlc退出不行&#xff0c;valgrind ctrlc退出也可以给出分析...

如何解决前端的竞态问题

前端的竞态问题通常是指多个异步操作的响应顺序与发起顺序不一致&#xff0c;导致程序出现不可预测的结果。这种问题在分页、搜索、选项卡切换等场景中尤为常见。以下是几种常见的解决方法&#xff1a; 1. 取消过期请求 当用户发起新的请求时&#xff0c;取消之前的请求&…...

(ECCV2018)CBAM改进思路

论文链接&#xff1a;https://arxiv.org/abs/1807.06521 论文题目&#xff1a;CBAM: Convolutional Block Attention Module 会议&#xff1a;ECCV2018 论文方法 利用特征的通道间关系生成了一个通道注意图。 由于特征映射的每个通道被认为是一个特征检测器&#xff0c;通道…...

Python脚本,音频格式转换 和 视频格式转换

一、音频格式转换完整代码 from pydub import AudioSegment import osdef convert_audio(input_dir, output_dir, target_format):if not os.path.exists(output_dir):os.makedirs(output_dir)for filename in os.listdir(input_dir):if filename.endswith((.mp3, .wav, .ogg)…...

基于Spring Boot的高校就业招聘系统的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…...

强化学习(赵世钰版)-学习笔记(4.值迭代与策略迭代)

本章是整个课程中&#xff0c;算法与方法的第一章&#xff0c;应该是最简单的入门方法。 上一章讲到了贝尔曼最优方程&#xff0c;其目的是计算出最优状态值&#xff0c;从而确定对应的最优策略。 而压缩映射理论推出了迭代算法 对初始值V0赋一个随机的初始值&#xff0c;算法最…...

Cursor安装配置

1.安装 通过网盘分享的文件&#xff1a;Cursor Setup 0.45.11 - x64.exe 链接: 百度网盘 请输入提取码 提取码: 6juv 2. 配置 选择AI工具的语言 输入AI工具的语言为 "中文" &#xff0c;输入完语言之后&#xff0c;直接点击 "Continue" 下一步&#x…...

相机几何:从三维世界到二维图像的映射

本系列课程将带领读者开启一场独特的三维视觉工程之旅。我们不再止步于教科书式的公式推导&#xff0c;而是聚焦于如何将抽象的数学原理转化为可落地的工程实践。通过解剖相机的光学特性、构建成像数学模型、解析坐标系转换链条&#xff0c;直至亲手实现参数标定代码&#xff0…...

【GoTeams】-5:引入Docker

本文目录 1. Dokcer-compose回顾下Docker知识编写docker-compose.yaml运行docker 2. 部署go服务编写dockerfile 1. Dokcer-compose 这里简单先用一下win版本的Docker&#xff0c;后期开发好了部署的时候再移植到服务器下进行docker部署。 输入命令docker-compose version 就可…...

基金股票期权期货投资方式对比

以下是基金、股票、期权和期货的详细对比分析&#xff0c;涵盖定义、核心特点、优势、劣势、适用场景及相互区别&#xff1a; 一、基金 定义 基金是通过集合投资者的资金&#xff0c;由专业管理人&#xff08;基金经理&#xff09;进行多元化投资的金融工具。根据投资标的可分…...

大模型AI平台DeepSeek 眼中的SQL2API平台:QuickAPI、dbapi 和 Magic API 介绍与对比

目录 1 QuickAPI 介绍 2 dbapi 介绍 3 Magic API 介绍 4 简单对比 5 总结 统一数据服务平台是一种低代码的方式&#xff0c;实现一般是通过SQL能直接生成数据API&#xff0c;同时能对产生的数据API进行全生命周期的管理&#xff0c;典型的SQL2API的实现模式。 以下是针对…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、切换节点 3&#xff09;、切换到 apparmor 的目录 4&#xff09;、执行 apparmor 策略模块 5&#xff09;、修改 pod 文件 6&#xff09;、…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统&#xff0c;智慧工地全套源码&#xff0c;java版智慧工地源码&#xff0c;支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求&#xff0c;提供“平台网络终端”的整体解决方案&#xff0c;提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…...

视觉slam十四讲实践部分记录——ch2、ch3

ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

Linux nano命令的基本使用

参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时&#xff0c;显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案

在大数据时代&#xff0c;海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构&#xff0c;在处理大规模数据抓取任务时展现出强大的能力。然而&#xff0c;随着业务规模的不断扩大和数据抓取需求的日益复杂&#xff0c;传统…...

第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10+pip3.10)

第一篇&#xff1a;Liunx环境下搭建PaddlePaddle 3.0基础环境&#xff08;Liunx Centos8.5安装Python3.10pip3.10&#xff09; 一&#xff1a;前言二&#xff1a;安装编译依赖二&#xff1a;安装Python3.10三&#xff1a;安装PIP3.10四&#xff1a;安装Paddlepaddle基础框架4.1…...