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

目录

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

前言

在这里插入图片描述
曾经遇见这么一段代码,能看出来是把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中的请求对象。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++20 格式化库:强大的字符串格式化工具

文章目录 格式化语法常见用法1. 填充和对齐2. 数值格式化3. 进制格式化4. 自定义类型 示例代码注意事项 C20 的格式化库是一个强大的工具&#xff0c;用于处理字符串的格式化操作。它提供了类似于 Python 中 str.format() 的功能&#xff0c;但语法和用法更符合 C 的风格。以下…

Linux基础--文件权限+软件包管理+管道符

目录 基础权限 更改文件权限 使用命令:chmod 更改文件属主和数组 使用命令: chown 权限掩码 使用命令:umask 高级权限 软件包管理 使用命令: rpm 使用命令: yum 管道符&#xff0c;重定向 基础权限 文件基础权限表 符号含义数字r读权限4w写权限2x执行权限1 更改文件…

css画出带圆角平行四边形效果

使用css画出平行四边形效果如下图 HTML代码 <div class"badge"><span>营业中</span> </div> 关键代码&#xff1a; transform: skewX(-15deg); /* 让元素倾斜&#xff0c;形成平行四边形的视觉效果 */ 如果倾斜的元素里面需要放文字&…

postman接口请求中的 Raw是什么

前言 在现代的网络开发中&#xff0c;API 的使用已经成为数据交换的核心方式之一。然而&#xff0c;在与 API 打交道时&#xff0c;关于如何发送请求体&#xff08;body&#xff09;内容类型的问题常常困扰着开发者们&#xff0c;尤其是“raw”和“json”这两个术语之间的区别…

Gartner:数据安全平台DSP提升数据流转及使用安全

2025 年 1 月 7 日&#xff0c;Gartner 发布“China Context&#xff1a;Market Guide for Data Security Platforms”&#xff08;《数据安全平台市场指南——中国篇》&#xff0c;以下简称指南&#xff09;&#xff0c;报告主要聚焦中国数据安全平台&#xff08;Data Securit…

记录一次wifi版有人物联串口服务器调试经过

1、首先买了一个华为的wifi路由器&#xff0c;连接上以后&#xff0c;设置好网络名字和wifi密码 2、用网线连接串口服务器&#xff0c;通过192.168.1.1登录&#xff0c;进行配置 找到无线客户端配置&#xff0c;先在基本配置中打开5G配置&#xff0c;然后再去5.8G配置中设置 …

百货店的诞生与现代商业革命:结合开源AI智能客服、AI智能名片与S2B2C商城小程序的新视角

摘要&#xff1a;本文深入探讨了百货店作为现代商业革命的标志性事件&#xff0c;其出现对销售方式、经营方式、组织管理三个方面的根本性变革。同时&#xff0c;本文也展望了在数字化时代背景下&#xff0c;开源AI智能客服、AI智能名片以及S2B2C商城小程序等新兴技术如何为传统…

初学STM32之简单认识IO口配置(学习笔记)

在使用51单片机的时候基本上不需要额外的配置IO&#xff0c;不过在使用特定的IO的时候需要额外的设计外围电路&#xff0c;比如PO口它是没有内置上拉电阻的。因此若想P0输出高电平&#xff0c;它就需要外接上拉电平。&#xff08;当然这不是说它输入不需要上拉电阻&#xff0c;…

HPC超算系列3——新手指南2

可以参考我的上一篇博客&#xff1a; https://blog.csdn.net/weixin_62528784/article/details/146122850?sharetypeblogdetail&sharerId146122850&sharereferPC&sharesourceweixin_62528784&spm1011.2480.3001.8118 这一节主要是对上一节的一些内容的补充&…

Ubuntu20.04搭建gerrit code review

一、环境准备 1. 安装 Java 环境‌ Gerrit 依赖 Java 运行环境&#xff08;推荐 JDK 8&#xff09;&#xff1a; sudo apt install openjdk-11-jdk 验证安装&#xff1a; java -version ‌2. 安装 Git sudo apt install git ‌3. 可选依赖 数据库‌&#xff1a;Gerrit …

爬虫案例七Python协程爬取视频

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Python协程爬取视频 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 爬虫案例七协程爬取视频 提示&#xff1a;以下是本篇文章正文…

GB28181视频监控流媒体平台LiveGBS如何自定义收流端口区间以便减少收流端口数或解决端口冲突问题

LiveGBS GB28181流媒体服务在接收视频的时候默认是使用30000-30249&#xff0c; webrtc流播放端口区间默认是UDP的30250-30500区间。有些网络环境不方便开放这么大的端口区间&#xff0c;下面介绍下如何修改配置这个区间。 从页面上修改这个区间&#xff0c;端口区间尽量设置大…

从连接到交互:SDN 架构下 OpenFlow 协议的流程与报文剖析

在SDN架构中&#xff0c;交换机与控制器之间的通信基于 OpenFlow协议&#xff0c;其设计目的是实现控制平面与数据平面的解耦。以下是 交换机连接控制器 和 数据包进入交换机触发交互 的详细流程及协议报文分析&#xff1a; 一、交换机连接控制器的流程&#xff08;初始化阶段&…

每日一练之反转链表

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

虚拟机总结| 关于虚拟机的一些配置总结

前言 每次安装新的虚拟机都需要重新在网上搜索如何配置网络&#xff0c;我需要写一个自己的部署步骤&#xff0c;增加工作效率&#xff0c;不用每次配置的时候再去网上去翻找。 1.只需要联网功能记录(不固定IP) 1.1 修改ifcfg-ens33 vi etc/sysconfig/network-scripts/ifcfg…

【数据结构初阶】---堆的实现、堆排序以及文件中的TopK问题

1.树的概念及结构 1.1树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结点&…

Autojs无线连接vscode方法

1.获得电脑的IP 在电脑的CMD界面输入 ipconfig 然后找到ipv4的那一行&#xff0c;后面的即是你的电脑IP地址 2.打开vscode的autojs服务 安装autojs插件 在vscode界面按下ctrlshiftp 输入autojs 找到 点击 之后打开手机上的autojs 之后输入刚刚电脑上的地址 可以看到vsc…

【Java开发指南 | 第三十五篇】Maven + Tomcat Web应用程序搭建

读者可订阅专栏&#xff1a;Java开发指南 |【CSDN秋说】 文章目录 前言Maven Tomcat Web应用程序搭建1、使用Maven构建新项目2、单击项目&#xff0c;连续按两次shift键&#xff0c;输入"添加"&#xff0c;选择"添加框架支持"3、选择Java Web程序4、点击&…

PyTorch深度学习框架60天进阶学习计划第16天:循环神经网络进阶!

PyTorch深度学习框架60天进阶学习计划 - 第16天&#xff1a;生成对抗网络原理 学习目标 今天我们将深入探讨生成对抗网络(GAN)的基本原理和数学基础&#xff0c;重点解析GAN的minimax博弈公式&#xff0c;推导生成器与判别器的损失函数&#xff0c;分析Wasserstein GAN的改进…

系统架构设计师—系统架构设计篇—微服务架构

文章目录 概述优势挑战 概述 微服务是一种架构风格&#xff0c;将单体应用划分成一组小的服务&#xff0c;服务之间相互协作&#xff0c;实现业务功能&#xff0c;每个服务运营在独立的进程中&#xff0c;服务间采用轻量级的通信机制协作&#xff08;通常是HTTP/JSON&#xff0…