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

What is `HttpServletRequestWrapper` does?

HttpServletRequestWrapperJava Servlet API 中的一个类,作为 HttpServletRequest 接口的包装器(Decorator)实现。

该类设计为装饰者模式(Decorator Pattern)的一部分,允许开发人员通过包装现有的 HttpServletRequest对象来定制或修改请求行为。比如:
过滤或修改请求参数
转换请求体数据
添加或删除请求头信息
实现请求级的安全控制,如防止 XSS(跨站脚本攻击)或 SQL 注入等安全风险
修改请求URI或其他请求属性

样例

import com.zhangziwa.practisesvr.utils.stream.StreamIUtils;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.text.StringEscapeUtils;

import java.io.*;

public class FilterHttpServletRequest extends HttpServletRequestWrapper {
    private final byte[] body;
    private ByteArrayInputStream byteArrayInputStream;
    private ServletInputStream servletInputStream;
    private BufferedReader bufferedReader;

    public FilterHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        body = StreamIUtils.readStream2Bytes(request.getInputStream());
        byteArrayInputStream = new ByteArrayInputStream(body);
    }

    public String getBody() throws UnsupportedEncodingException {
        String characterEncoding = this.getCharacterEncoding();
        return new String(body, characterEncoding);
    }

    /**
     * 覆盖父类方法,实现获取参数时自动对参数值进行XSS攻击过滤。
     *
     * @param name 参数名
     * @return 如果参数存在,则返回经过HTML转义的、已过滤XSS攻击的参数值;若参数不存在,则返回null
     */
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        if (value != null) {
            // 对请求参数值进行XSS过滤
            return StringEscapeUtils.escapeHtml4(value);
        }
        return null;
    }

    /**
     * 重写父类的getCharacterEncoding方法,获取请求的字符编码。
     * 若当前请求的字符编码未设置或为空,则默认返回"utf-8"作为字符编码。
     *
     * @return 请求的字符编码,若原编码为空则返回"utf-8"
     */
    @Override
    public String getCharacterEncoding() {
        // 调用父类的getCharacterEncoding方法获取字符编码
        String encoding = super.getCharacterEncoding();
        // 如果字符编码为空,则默认为utf-8
        return encoding == null ? "utf-8" : encoding;
    }

    /**
     * 重写父类或接口中的getReader方法,提供一个BufferedReader对象。
     *
     * @return 返回一个根据请求体内容和字符编码方式创建的BufferedReader对象
     */
    @Override
    public BufferedReader getReader() {
        // 如果输入流尚未初始化,则使用请求体内容创建一个新的ByteArrayInputStream
        if (byteArrayInputStream == null) {
            byteArrayInputStream = new ByteArrayInputStream(body);
        }

        // 如果BufferedReader还未创建,则进行以下逻辑:
        if (bufferedReader == null) {
            // 获取当前请求的字符编码方式
            String characterEncoding = getCharacterEncoding();

            try {
                // 使用获取到的字符编码方式以及已有的ByteArrayInputStream创建一个InputStreamReader对象
                // 并在此基础上封装一个BufferedReader对象以提高读取效率
                bufferedReader = new BufferedReader(new InputStreamReader(byteArrayInputStream, characterEncoding));
            } catch (UnsupportedEncodingException e) {
                // 若遇到不支持的字符编码异常,捕获并抛出一个包含详细错误信息的RuntimeException
                throw new RuntimeException("Unsupported encoding: " + characterEncoding, e);
            }
        }

        // 返回已经创建好的BufferedReader对象
        return bufferedReader;
    }

    /**
     * 重写父类的 getInputStream 方法,提供一个自定义的 ServletInputStream 实例,
     * 该实例从内部的 byteArrayInputStream 中读取数据,并支持监听器模式。
     *
     * @return 自定义的 ServletInputStream 实例,用于读取请求体数据
     */
    @Override
    public ServletInputStream getInputStream() {
        // 确保 servletInputStream 的初始化在多线程环境下是安全的
        if (servletInputStream == null) {
            synchronized (this) {
                // 创建并初始化一个 ServletInputStream 子类实例
                servletInputStream = new ServletInputStream() {
                    /**
                     * 从内部的 byteArrayInputStream 中读取下一个字节数据
                     *
                     * @return 下一个可读字节,如果已到达流末尾则返回 -1
                     * @throws IOException 如果发生输入/输出错误
                     */
                    @Override
                    public int read() throws IOException {
                        return byteArrayInputStream.read();
                    }

                    /**
                     * 判断是否已经读取完所有数据,即 byteArrayInputStream 是否还有可用数据
                     *
                     * @return 如果没有更多数据可供读取,则返回 true;否则返回 false
                     */
                    public boolean isFinished() {
                        return byteArrayInputStream.available() == 0;
                    }

                    /**
                     * 指示此输入流是否准备好进行读取操作
                     *
                     * @return 始终返回 true,表示此输入流始终处于就绪状态
                     */
                    public boolean isReady() {
                        return true;
                    }

                    /**
                     * 设置 ReadListener 监听器,用于异步读取数据。此处未实现具体逻辑。
                     *
                     * @param readListener 用于处理数据读取事件的 ReadListener 实例
                     */
                    @Override
                    public void setReadListener(ReadListener readListener) {
                        // 留给子类实现
                    }
                };
            }
        }
        // 返回已初始化的 servletInputStream
        return this.servletInputStream;
    }
}

[Ref] StreamIUtils 共通方法最佳实践

相关文章:

  • Mac ❀ 如何在MacOS上安装pip软件包
  • 为什么要用B+树
  • 初识MySQL
  • chrome 307状态码
  • Elasticsearch:聊天机器人教程(二)
  • 基于Docker官方php:7.4.33-fpm镜像构建支持67个常见模组的php7.4.33镜像
  • AI对决:ChatGPT与文心一言的比较
  • 2024年【陕西省安全员A证】考试题及陕西省安全员A证模拟考试题库
  • C++(1) —— 基础语法入门
  • 【生存技能】git操作
  • 文心一言 vs. ChatGPT:哪个更胜一筹?
  • linux安装QQ(官方正版)
  • wins安装paddle框架
  • 【数据库原理】(29)数据库设计-需求分析阶段
  • 蓝桥杯准备
  • 【K8s学习】
  • Docker RTMP服务器搭建与视频流推送示例(流媒体服务器tiangolo/nginx-rtmp,推流客户端ffmpeg)
  • 如何在网络爬虫中解决CAPTCHA?使用Python进行网络爬虫
  • 学习k8s的应用(三)
  • 设计模式——模板方法模式
  • 中国科学院院士、我国航天液体火箭技术专家朱森元逝世
  • 沙青青评《通勤梦魇》︱“人机组合”的通勤之路
  • 外企聊营商|特雷通集团:税务服务“及时雨”
  • 中国至越南河内国际道路运输线路正式开通
  • 中保协发布《保险机构适老服务规范》,全面规范保险机构面向老年人提供服务的统一标准
  • 第1现场 | 印巴停火次日:当地民众逐渐恢复正常生活