当前位置: 首页 > 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的实现模式。 以下是针对…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

postgresql|数据库|只读用户的创建和删除(备忘)

CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...

docker 部署发现spring.profiles.active 问题

报错&#xff1a; org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...

python爬虫——气象数据爬取

一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用&#xff1a; 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests&#xff1a;发送 …...

Kafka主题运维全指南:从基础配置到故障处理

#作者&#xff1a;张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1&#xff1a;主题删除失败。常见错误2&#xff1a;__consumer_offsets占用太多的磁盘。 主题日常管理 …...

Axure 下拉框联动

实现选省、选完省之后选对应省份下的市区...

ZYNQ学习记录FPGA(一)ZYNQ简介

一、知识准备 1.一些术语,缩写和概念&#xff1a; 1&#xff09;ZYNQ全称&#xff1a;ZYNQ7000 All Pgrammable SoC 2&#xff09;SoC:system on chips(片上系统)&#xff0c;对比集成电路的SoB&#xff08;system on board&#xff09; 3&#xff09;ARM&#xff1a;处理器…...