对HttpServletRequest中的Header进行增删

简介: 本文介绍了如何通过反射操作Servlet容器中的请求头,弥补`HttpServletRequest`接口未提供修改或删除功能的不足。以Tomcat和Undertow为例,展示了如何在Filter中对请求头进行增删操作,如添加`x-request-id`和移除`User-Agent`等。适用于需要统一处理请求头的场景。[详细步骤及代码示例](https://forum.springdoc.cn/t/topic/3643)。

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

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

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

相关文章
|
Java
如何修改HttpServletRequest的Headers?
HttpServletRequest java
3683 0
vite环境引入web worker方法
在 vite 环境中使用 web worker 时,如果遇到生产环境中 worker.js 文件的 MIME 类型被识别为 text/html,导致报错无法运行的情况时,可以参考以下两种方法,原理都是避免编译时产出单独的 worker.js 文件。方法一worker文件不需要包装,引入时后缀增加 ?worker&inline,使用时直接 new ImportedWorker();self.
1768 1
|
消息中间件 Java 应用服务中间件
Kafka配置公网访问,直接暴露方式与nginx代理方式(绝对没问题)
Kafka配置公网访问,直接暴露方式与nginx代理方式(绝对没问题)
5918 1
|
9月前
|
Java API 微服务
微服务——SpringBoot使用归纳——Spring Boot中的切面AOP处理——Spring Boot 中的 AOP 处理
本文详细讲解了Spring Boot中的AOP(面向切面编程)处理方法。首先介绍如何引入AOP依赖,通过添加`spring-boot-starter-aop`实现。接着阐述了如何定义和实现AOP切面,包括常用注解如`@Aspect`、`@Pointcut`、`@Before`、`@After`、`@AfterReturning`和`@AfterThrowing`的使用场景与示例代码。通过这些注解,可以分别在方法执行前、后、返回时或抛出异常时插入自定义逻辑,从而实现功能增强或日志记录等操作。最后总结了AOP在实际项目中的重要作用,并提供了课程源码下载链接供进一步学习。
1122 0
|
9月前
|
NoSQL Java API
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Spring Boot 集成 Redis
本文介绍了在Spring Boot中集成Redis的方法,包括依赖导入、Redis配置及常用API的使用。通过导入`spring-boot-starter-data-redis`依赖和配置`application.yml`文件,可轻松实现Redis集成。文中详细讲解了StringRedisTemplate的使用,适用于字符串操作,并结合FastJSON将实体类转换为JSON存储。还展示了Redis的string、hash和list类型的操作示例。最后总结了Redis在缓存和高并发场景中的应用价值,并提供课程源代码下载链接。
2109 0
|
12月前
|
人工智能 前端开发 Java
Spring AI Alibaba + 通义千问,开发AI应用如此简单!!!
本文介绍了如何使用Spring AI Alibaba开发一个简单的AI对话应用。通过引入`spring-ai-alibaba-starter`依赖和配置API密钥,结合Spring Boot项目,只需几行代码即可实现与AI模型的交互。具体步骤包括创建Spring Boot项目、编写Controller处理对话请求以及前端页面展示对话内容。此外,文章还介绍了如何通过添加对话记忆功能,使AI能够理解上下文并进行连贯对话。最后,总结了Spring AI为Java开发者带来的便利,简化了AI应用的开发流程。
9111 2
Spring AI Alibaba + 通义千问,开发AI应用如此简单!!!
|
缓存 Java Spring
servlet和SpringBoot两种方式分别获取Cookie和Session方式比较(带源码) —— 图文并茂 两种方式获取Header
文章比较了在Servlet和Spring Boot中获取Cookie、Session和Header的方法,并提供了相应的代码实例,展示了两种方式在实际应用中的异同。
1421 3
servlet和SpringBoot两种方式分别获取Cookie和Session方式比较(带源码) —— 图文并茂 两种方式获取Header
|
监控 网络协议 应用服务中间件
【Tomcat源码分析】从零开始理解 HTTP 请求处理 (第一篇)
本文详细解析了Tomcat架构中复杂的`Connector`组件。作为客户端与服务器间沟通的桥梁,`Connector`负责接收请求、封装为`Request`和`Response`对象,并传递给`Container`处理。文章通过四个关键问题逐步剖析了`Connector`的工作原理,并深入探讨了其构造方法、`init()`与`start()`方法。通过分析`ProtocolHandler`、`Endpoint`等核心组件,揭示了`Connector`初始化及启动的全过程。本文适合希望深入了解Tomcat内部机制的读者。欢迎关注并点赞,持续更新中。如有问题,可搜索【码上遇见你】交流。
【Tomcat源码分析】从零开始理解 HTTP 请求处理 (第一篇)
|
Kubernetes 负载均衡 应用服务中间件
k8s学习--ingress详细解释与应用(nginx ingress controller))
k8s学习--ingress详细解释与应用(nginx ingress controller))
2272 0
|
Java Spring
【Java】Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean
【Java】Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean
843 0