对HttpServletRequest中的Header进行增删

news2024/9/23 11:59:33

HttpServletRequest 没有提供修改/删除的 Api


HttpServletRequest中定义的对 Header 的操作全是只读,没有修改。代码实现如下:

public interface HttpServletRequest extends ServletRequest {
    ...
    public long getDateHeader(String name);
    public String getHeader(String name);
    public Enumeration<String> getHeaders(String name);
    public Enumeration<String> getHeaderNames();
    public int getIntHeader(String name);
    ...
}

HttpServletRequest 只是一个接口,实现由 Servlet 容器提供。不管是任何容器,实现类,肯定是要把请求的 Header 存储在某个地方,于是可以通过反射来对存储Header的容器进行增删。

先定义一个测试的 Controller

这个 Controller 很简单,把客户端的所有 Header,以 JSON 形似响应给客户端。

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/demo")
public class DemoController {

	// 遍历所有请求Header,响应给客户端。 map<string, string[]>
	@GetMapping
	public Object demo (HttpServletRequest request) {
		Map<String, List<String>> headers = new LinkedHashMap<>();
		Enumeration<String> nameEnumeration = request.getHeaderNames();
		while (nameEnumeration.hasMoreElements()) {
			String name = nameEnumeration.nextElement();
			List<String> values = headers.get(name);
			if (values == null) {
				values = new ArrayList<>();
				headers.put(name, values);
			}
			Enumeration<String> valueEnumeration = request.getHeaders(name);
			while (valueEnumeration.hasMoreElements()) {
				values.add(valueEnumeration.nextElement());
			}
		}
		return headers;
	}
}

使用 Tomcat 作为容器

Tomcat 对 HttpServletRequest 的实现

Tomcat 使用了外观模式(Facade),这个实现稍微有一点点复杂

org.apache.catalina.connector.RequestFacade
|-org.apache.catalina.connector.Request
  |-org.apache.coyote.Request
    |-org.apache.tomcat.util.http.MimeHeaders

首先是 org.apache.catalina.connector.RequestFacade 实现,它有一个org.apache.catalina.connector.Request 的对象。这个对象又有一个org.apache.coyote.Request的对象,这个对象又有一个org.apache.tomcat.util.http.MimeHeaders 字段,它就是存储了客户端请求头的容器,只要通过反射获取到这个MimeHeaders,对它进行修改即可。

org.apache.catalina.connector.RequestFacade
public class RequestFacade implements HttpServletRequest {
    protected org.apache.catalina.connector.Request request = null;
    ...
}
org.apache.catalina.connector.Request
public class Request implements HttpServletRequest {
    protected org.apache.coyote.Request coyoteRequest;
    ...
}
org.apache.coyote.Request coyoteRequest
public final class Request {
    private final org.apache.tomcat.util.http.MimeHeaders headers = new MimeHeaders();
}

在 Filter 中通过反射对请求 Header 进行增删

假设的场景是,需要对请求 Header 统一添加一个x-request-id,通过这个 ID 来从日志中定位每一个请求。

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.UUID;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.RequestFacade;
import org.apache.tomcat.util.http.MimeHeaders;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;



@WebFilter(urlPatterns = "/*")
@Component
@Order(-999)
public class RequestIdGenFilter extends HttpFilter {

	/**
	 *
	 */
	private static final long serialVersionUID = 1787347739651657706L;

	@Override
	protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
		try {
			// 从 RequestFacade 中获取 org.apache.catalina.connector.Request
			Field connectorField = ReflectionUtils.findField(RequestFacade.class, "request", Request.class);
			connectorField.setAccessible(true);
			Request connectorRequest = (Request) connectorField.get(req);

			// 从 org.apache.catalina.connector.Request 中获取 org.apache.coyote.Request
			Field coyoteField = ReflectionUtils.findField(Request.class, "coyoteRequest", org.apache.coyote.Request.class);
			coyoteField.setAccessible(true);
			org.apache.coyote.Request coyoteRequest = (org.apache.coyote.Request) coyoteField.get(connectorRequest);

			// 从 org.apache.coyote.Request 中获取 MimeHeaders
			Field mimeHeadersField =  ReflectionUtils.findField(org.apache.coyote.Request.class, "headers", MimeHeaders.class);
			mimeHeadersField.setAccessible(true);
			MimeHeaders mimeHeaders =  (MimeHeaders) mimeHeadersField.get(coyoteRequest);

			this.mineHeadersHandle(mimeHeaders);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		super.doFilter(req, res, chain);
	}

	protected void mineHeadersHandle (MimeHeaders mimeHeaders) {
		// 添加一个Header,随机生成请求ID
		mimeHeaders.addValue("x-request-id").setString(UUID.randomUUID().toString());;
		// 移除一个header
		mimeHeaders.removeHeader("User-Agent");
	}
}

请求 Controller 获取响应结果

可以看到成功添加了x-request-id header,并且删除了User-Agent header。

使用 Undertow 作为容器

越来越多人使用 Undertow 作为 Servlet 容器,据说性能比 Tomcat 要好很多

SpringBoot 替换 Tomcat 为 Undertow

只需要把spring-boot-starter-web中的spring-boot-starter-tomcat排除,然后手动添加spring-boot-starter-undertow即可

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
      <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
  </dependency>
</dependencies>

Undertow 中的HttpServletRequest实现

它的实现就比较简单,结构如下:

io.undertow.servlet.spec.HttpServletRequestImpl
|-io.undertow.server.HttpServerExchange
  |-io.undertow.util.HeaderMap

io.undertow.servlet.spec.HttpServletRequestImpl实现类中有一个属性对象

io.undertow.server.HttpServerExchange,这个属性对象又包含了一个io.undertow.util.HeaderMapHeaderMap就是请求 Header 的存储容器,反射获取它就行。

io.undertow.servlet.spec.HttpServletRequestImpl
public final class HttpServletRequestImpl implements HttpServletRequest {
    private final io.undertow.server.HttpServerExchange exchange;
}
io.undertow.server.HttpServerExchange
public final class HttpServerExchange extends AbstractAttachable {
    private final HeaderMap requestHeaders;
}

在 Filter 中通过反射对请求 Header 进行增删

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.UUID;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import io.undertow.server.HttpServerExchange;
import io.undertow.servlet.spec.HttpServletRequestImpl;
import io.undertow.util.HeaderMap;
import io.undertow.util.HttpString;



@WebFilter(urlPatterns = "/*")
@Component
@Order(-999)
public class RequestIdGenFilter extends HttpFilter {

	/**
	 *
	 */
	private static final long serialVersionUID = 1787347739651657706L;

	@Override
	protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
		try {
			// 从HttpServletRequestImpl中获取HttpServerExchange
			Field exchangeField = ReflectionUtils.findField(HttpServletRequestImpl.class, "exchange", HttpServerExchange.class);
			exchangeField.setAccessible(true);
			HttpServerExchange httpServerExchange = (HttpServerExchange) exchangeField.get(req);

			// 从HttpServerExchange中获取HeaderMap
			Field headerMapField = ReflectionUtils.findField(HttpServerExchange.class, "requestHeaders", HeaderMap.class);
			headerMapField.setAccessible(true);

			HeaderMap requestHeaderMap = (HeaderMap) headerMapField.get(httpServerExchange);
			this.handleRequestHeaderMap(requestHeaderMap);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		super.doFilter(req, res, chain);
	}

	private void handleRequestHeaderMap(HeaderMap requestHeaderMap) {
		// 添加Header
		requestHeaderMap.add(new HttpString("x-request-id"), UUID.randomUUID().toString());
		// 移除Header
		requestHeaderMap.remove("User-Agent");
	}
}

请求 Controller 获取结果

结语

还有其他的 Servlet 容器,例如 Jetty。只要熟悉基本的反射,实现对请求头的增删,都很简单。

原文链接:[https://forum.springdoc.cn/t/topic/3643]

如有问题,欢迎微信搜索【码上遇见你】。

好了,本章节到此告一段落。希望对你有所帮助,祝学习顺利。

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

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

相关文章

Isaac Sim 4.2.0 Windows版本打开报 fbgemm.dll 加载错误

方案一&#xff1a;下载缺少的dll复制到目录里即可 可以看到后台命令窗口出现了错误&#xff0c;发生在import pytorch的时候&#xff0c;根据提示&#xff0c;是因为fbgemm.dll缺少依赖&#xff0c;导致加载异常&#xff0c;一般情况是缺少 libomp140.x86_64.dll 这个文件&am…

前端工程化4:从0到1构建完整的前端监控平台

前言 一套完整的前端监控系统的主要部分&#xff1a; 数据上报方式数据上送时机性能数据采集错误数据采集用户行为采集定制化指标监控sdk 监控的目的&#xff1a; 一、数据上报方式 本文的方案是&#xff0c;优先navigator.sendBeacon&#xff0c;降级使用1x1像素gif图片…

C语言 | Leetcode C语言题解之第419题棋盘上的战舰

题目&#xff1a; 题解&#xff1a; int countBattleships(char** board, int boardSize, int* boardColSize){int row boardSize;int col boardColSize[0];int ans 0;for (int i 0; i < row; i) {for (int j 0; j < col; j) {if (board[i][j] X) {if (i > 0 &…

手机在网状态查询接口如何用PHP进行调用?

一、什么是手机在网状态查询接口&#xff1f; 手机在网状态查询接口&#xff0c;即输入手机号码查询手机号在网状态&#xff0c;返回有正常使用、停机、在网但不可用、不在网&#xff08;销号/未启用/异常&#xff09;、预销户等多种状态。 二、手机在网状态查询适用哪些场景…

C++ | Leetcode C++题解之第429题N叉树的层序遍历

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<vector<int>> levelOrder(Node* root) {if (!root) {return {};}vector<vector<int>> ans;queue<Node*> q;q.push(root);while (!q.empty()) {int cnt q.size();vector<…

Comfyui海报工作流:出图快,质量高!

前言 工作流获取方式放在这里了 在快节奏的现代生活中&#xff0c;高效的工作流程对于企业和个人而言&#xff0c;无疑是提升竞争力的关键。 特别是在设计领域&#xff0c;能够快速而精准地完成海报设计&#xff0c;不仅意味着时间的节省&#xff0c;更代表着工作效率的飞跃。…

分治(快排系列)

一、基本思路 传统快排只是把数组分成两段进行排序&#xff0c;但是这样遇到重复数字多的数组就会超时&#xff0c;所以引入数组分三块&#xff1a; 流程&#xff1a; 1、初始化&#xff1a;l -1, r nums.size(); 2、先随机数选出 key 作为基准元素。srand(time(NULL)); in…

路径处理 | 关键点提取之Douglas–Peucker算法(附ROS C++/Python实现)

目录 0 专栏介绍1 路径关键点提取2 道格拉斯-普克算法Douglas–Peucker3 算法实现与可视化3.1 ROS C仿真3.2 Python仿真 0 专栏介绍 &#x1f525;课设、毕设、创新竞赛必备&#xff01;&#x1f525;本专栏涉及更高阶的运动规划算法轨迹优化实战&#xff0c;包括&#xff1a;…

深入理解并发原子性、可见性、有序性与JMM内存模型

1. 并发三大特性 并发编程Bug的源头&#xff1a;原子性、可见性和有序性问题 1.1 原子性 一个或多个操作&#xff0c;要么全部执行且在执行过程中不被任何因素打断&#xff0c;要么全部不执行。在 Java 中&#xff0c;对基本数据类型的变量的读取和赋值操作是原子性操作&…

HTB-GreenHorn 靶机笔记

GreenHorn 靶机笔记 概述 GreenHorn 是 HTB 上的一个 linux easy 难度的靶机&#xff0c;主要是通过信息搜集和代码审计找到对我们有用的信息。其中还包含了对pdf文件的修复技术 靶机地址&#xff1a;https://app.hackthebox.com/machines/GreenHorn 一丶 nmap 扫描 1&…

Spring Boot 学习之路 -- 基础认知

前言 最近因为业务需要&#xff0c;被拉去研究后端的项目&#xff0c;代码基于 Spring Boot&#xff0c;对我来说完全小白&#xff0c;需要重新学习研究…出于个人习惯&#xff0c;会以 Blog 文章的方式做一些记录&#xff0c;文章内容基本来源于「 Spring Boot 从入门到精通&…

Mac系统Docker中SQLserver数据库文件恢复记录

Mac系统Docker中SQLserver数据库文件恢复记录 Mac想要安装SQLsever&#xff0c;通过docker去拉去镜像是最简单方法。 一、下载Docker Docker 下载安装&#xff1a; 需要‘科学上网’ 才能访问到docker官网。&#xff08; https://docs.docker.com/desktop/install/mac-ins…

短剧APP分销小视频联盟收益源码带版权激励视频无需自己上传短剧

功能介绍&#xff1a; 带2000多部短剧资源&#xff0c;有版权&#xff0c;无需自己更新短剧&#xff0c; 已对接广告联盟&#xff0c;解锁短剧观看激励视频&#xff0c;对接各大广告平台 带刷小视频功能&#xff0c;插入视频广告&#xff0c;获取广告收益&#xff0c; 带任…

Stable Diffusion绘画 | ControlNet应用-instant-ID控制器:快速生成人物多角度图片

使用 instant-ID 控制器&#xff0c;用户只需要提供一张正脸图片&#xff0c;就可以快速地给人物生成多角度图片的&#xff0c;从而很好的保持了人物的一致性。 对于要制作小说推文、创建人物故事情节的创作&#xff0c;是一个非常好用且高效的功能。 准备工作 使用该控制类型&…

什么是端到端(end to end)大模型,它和传统的大模型有什么区别?其优势与劣势是什么?

“ 端到端模型&#xff0c;是一个直接由输入获取输出的过程 ” 最近有一个很火的关于人工智能模型的词——端到端模型。 那么什么是端到端模型&#xff1f;为什么会提出端到端模型&#xff0c;以及它解决了哪些问题&#xff1f; 今天我们就来一起了解一下这个端到端模型。 …

如何使用ChatGPT撰写文献综述?7个步骤轻松搞定

大家好,感谢关注。我是七哥,一个在高校里不务正业,折腾学术科研AI实操的学术人。关于使用ChatGPT等AI学术科研的相关问题可以和作者七哥(yida985)交流,多多交流,相互成就,共同进步,为大家带来最酷最有效的智能AI学术科研写作攻略。 撰写文献综述对于研究人员和学生来说…

ssm汉服文化平台网站

专业团队&#xff0c;咨询就送开题报告&#xff0c;欢迎大家咨询&#xff0c;联系方式在文章底部 摘 要 本论文主要论述了如何使用JAVA语言开发一个汉服文化平台网站 &#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象…

【题解】—— LeetCode一周小结38

&#x1f31f;欢迎来到 我的博客 —— 探索技术的无限可能&#xff01; &#x1f31f;博客的简介&#xff08;文章目录&#xff09; 【题解】—— 每日一道题目栏 上接&#xff1a;【题解】—— LeetCode一周小结37 16.公交站间的距离 题目链接&#xff1a;1184. 公交站间的距…

【Linux】入门【更详细,带实操】

Linux全套讲解系列&#xff0c;参考视频-B站韩顺平&#xff0c;本文的讲解更为详细 目录 1、课程内容 2、应用领域 3、概述 4、 Linux和Unix 5、VMware15.5和CentOS7.6安装 6、网络连接三种方式 7、虚拟机克隆 8、虚拟机快照 9、虚拟机迁移删除 10、vmtools 11、目录…

抱歉占用公共资源,大家别猜啦,我们在一起了@Yaker

家人们上午好呀 这里是超绝脱单牛一枚 没错&#xff0c;我和Yaker有一个孩子&#xff08;bushi 今天我们的孩子YakLang来给大家介绍介绍&#xff0c;ta对块作用域的处理方式 在编程中&#xff0c;作用域&#xff08;Scope&#xff09;指的是变量、函数和对象的可访问性和生命…