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

news2025/3/9 9:16:18

目录

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

前言

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

后面了解到这叫

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

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

一、场景

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

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

	 @Override
	    public 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);
        // 缓存请求body
        try {
            requestBody = StreamUtils.copyToByteArray(request.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (requestBody == null) {
            requestBody = new byte[0];
        }
        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return bais.read();
            }

            @Override
            public boolean isFinished() {
                // TODO Auto-generated method stub
                return true;
            }

            @Override
            public boolean isReady() {
                // TODO Auto-generated method stub
                return true;
            }

            @Override
            public void setReadListener(ReadListener listener) {
                // TODO Auto-generated method stub
            }
        };
    }

    @Override
    public 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 {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public 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);
    }

 	 @Override
    public 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);
    }

    @Override
    public String name()
    {
        return "CacheRequestFilter";
    }

    @Override
    public 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
    {
        @Override
        public 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());
            });
        }
    }

    @Override
    public 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中的请求对象。

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

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

相关文章

STM32-I2C通信协议

目录 一&#xff1a;什么是I2C通信协议 二&#xff1a;I2C通信 三&#xff1a;I2C时序图 四&#xff1a;面试常见问题 一&#xff1a;什么是I2C通信协议 I2C&#xff08;Inter-Integrated Circuit&#xff09;协议是一种串口通信协议&#xff0c;用于在集成电路之间传输数…

Unity开发——CanvasGroup组件介绍和应用

CanvasGroup是Unity中用于控制UI的透明度、交互性和渲染顺序的组件。 一、常用属性的解释 1、alpha&#xff1a;控制UI的透明度 类型&#xff1a;float&#xff0c;0.0 ~1.0&#xff0c; 其中 0.0 完全透明&#xff0c;1.0 完全不透明。 通过调整alpha值可以实现UI的淡入淡…

DeepSeek开启AI办公新模式,WPS/Office集成DeepSeek-R1本地大模型!

从央视到地方媒体&#xff0c;已有多家媒体机构推出AI主播&#xff0c;最近杭州文化广播电视集团的《杭州新闻联播》节目&#xff0c;使用AI主持人进行新闻播报&#xff0c;且做到了0失误率&#xff0c;可见AI正在逐渐取代部分行业和一些重复性的工作&#xff0c;这一现象引发很…

C语言100天练习题【记录本】

C语言经典100题&#xff08;手把手 编程&#xff09; 可以在哔哩哔哩找到&#xff08;url:C语言经典100题&#xff08;手把手 编程&#xff09;_哔哩哔哩_bilibili&#xff09; 已解决的天数&#xff1a;一&#xff0c;二&#xff0c;五&#xff0c;六&#xff0c;八&#xf…

DeepSeek【部署 03】客户端应用ChatBox、AnythingLLM及OpenWebUI部署使用详细步骤

DeepSeek客户端应用 1.ChatBox2.AnythingLLM3.OpenWebUI4.总结 客户端软件提供可视化的模型及参数配置&#xff0c;人性化的对话窗口及文件上传功能&#xff0c;大大降低了大模型的使用门槛。 1.ChatBox Chatbox AI 是一款 AI 客户端应用和智能助手&#xff0c;支持众多先进的…

Python图形编程之EasyGUI: msgbox的用法

1 EasyGUI: msgbox的用法 1.1 基础用法&#xff1a;只显示信息 示例代码&#xff1a; from easygui import * msgbox("Hello, world!")效果&#xff1a; 1.2 扩展用法1&#xff1a;设置标题 示例代码&#xff1a; from easygui import * msgbox("Hello, …

中性点直接接地电网接地故障Simulink仿真

1.模型简介 本仿真模型基于MATLAB/Simulink&#xff08;版本MATLAB 2017Ra&#xff09;软件。建议采用matlab2017 Ra及以上版本打开。&#xff08;若需要其他版本可联系代为转换&#xff09; 2.系统仿真图&#xff1a; 3.中性点直接接地电网接地故障基本概念&#xff08;本仿…

解决Jenkins默认终止Shell产生服务进程的问题

1、Windows环境 Jenkins进行Build steps的使用Execute Windows batch command启动微服务&#xff08;Jar包&#xff09;&#xff0c;Jenkins会默认终止Shell产生的服务进程&#xff0c;而在命令行能够正常运行的服务进程。 1.1 使用命令行启动服务是正常 使用命令行执行 正常…

RuleOS:区块链开发的“破局者”,开启Web3新纪元

RuleOS&#xff1a;区块链开发的“破冰船”&#xff0c;驶向Web3的星辰大海 在区块链技术的浩瀚宇宙中&#xff0c;一群勇敢的探索者正驾驶着一艘名为RuleOS的“破冰船”&#xff0c;冲破传统开发的冰层&#xff0c;驶向Web3的星辰大海。这艘船&#xff0c;正以一种前所未有的姿…

OpenCV 拆分、合并图像通道方法及复现

视频讲解 OpenCV 拆分、合并图像通道方法及复现 环境准备&#xff1a;安装 OpenCV 库&#xff08;pip install opencv-python&#xff09; 内容&#xff1a; 1. 读取任意图片&#xff08;支持 jpg/png 等格式&#xff09; 2. 使用 split () 函数拆解成 3 个单色通道&#xf…

基于Python实现的智能旅游推荐系统(Django)

基于Python实现的智能旅游推荐系统(Django) 开发语言:Python 数据库&#xff1a;MySQL所用到的知识&#xff1a;Django框架工具&#xff1a;pycharm、Navicat 系统功能实现 总体设计 系统实现 系统首页模块 统首页页面主要包括首页&#xff0c;旅游资讯&#xff0c;景点信息…

C++--迭代器(iterator)介绍---主要介绍vector和string中的迭代器

目录 一、迭代器&#xff08;iterator&#xff09;的定义 二、迭代器的类别 三、使用迭代器 3.1 迭代器运算符 3.2 迭代器的简单应用&#xff1a;使用迭代器将string对象的第一个字母改为大写 3.3 将迭代器从一个元素移动到另外一个元素 3.4 迭代器运算 3.5 迭代器的复…

SpringCloud——Consul服务注册与发现

一、为什么要引入服务注册中心 &#xff08;1&#xff09;为什么引入 微服务硬编码 IP / 端口的核心问题总结 环境变更敏感&#xff1a;当支付微服务的 IP 或端口修改时&#xff0c;订单微服务必须同步修改所有调用该支付服务的代码或配置&#xff0c;否则将无法正常通信无法…

C语言_数据结构总结5:顺序栈

纯C语言代码&#xff0c;不涉及C 想了解链式栈的实现&#xff0c;欢迎查看这篇文章&#xff1a;C语言_数据结构总结6&#xff1a;链式栈-CSDN博客 这里分享插入一下个人觉得很有用的习惯&#xff1a; 1. 就是遇到代码哪里不理解的&#xff0c;你就问豆包&#xff0c;C知道&a…

人工智能之数学基础:正交矩阵

本文重点 正交矩阵是线性代数中一个重要的特殊矩阵&#xff0c;它在许多领域都有广泛的应用。 什么是正交矩阵 如图所示&#xff0c;当矩阵A满足如上所示的条件的时候&#xff0c;此时我们就可以认为是正交矩阵&#xff0c;需要注意一点矩阵A必为方阵。 正交矩阵的充要条件 …

抓包分析工具介绍

什么是抓包分析工具&#xff1f; 抓包分析工具&#xff0c;也称为网络数据包嗅探器或协议分析器&#xff0c;用于捕获和检查网络上传输的数据包。这些数据包包含了网络通信的详细信息&#xff0c;例如请求的资源、服务器的响应、HTTP 头信息、传输的数据内容等等。通过分析这些…

2025/3/8 第 27 场 蓝桥入门赛 题解

1. 38红包【算法赛】 签到题&#xff1a; 算倍数就行了 #include <bits/stdc.h> using namespace std; int main() {int ans0;for(int i1;i<2025;i){if(i % 3 0)ans;else if(i % 8 0)ans;else if(i % 38 0)ans;}cout<<ans<<endl;return 0; } 2. 祝福…

使用Node.js从零搭建DeepSeek本地部署(Express框架、Ollama)

目录 1.安装Node.js和npm2.初始化项目3.安装Ollama4.下载DeepSeek模型5.创建Node.js服务器6.运行服务器7.Web UI对话-Chrome插件-Page Assist 1.安装Node.js和npm 首先确保我们机器上已经安装了Node.js和npm。如果未安装&#xff0c;可以通过以下链接下载并安装适合我们操作系…

deepseek 3FS编译

3FS在ubuntu22.04下的编译&#xff08;记录下编译过程&#xff0c;方便后续使用&#xff09; 环境信息 OS ubuntu 22.04内核版本 6.8.0-52-genericlibfuse 3.16.1rust 1.75.0FoundationDB 7.1.66meson 1.0.0ninja 1.10.1 libfuse编译 以下建议均在root下执行 pip3 install…

每日一练之移除链表元素

题目&#xff1a; 画图解析&#xff1a; 方法&#xff1a;双指针 解答代码&#xff08;注&#xff1a;解答代码带解析&#xff09;&#xff1a; //题目给的结构体 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* }…