51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

在 HttpServletRequest 中添加自定义请求头

HTTP 请求头一般都是由客户端设置,服务端获取,用以处理业务。但是偶尔也有一些需求,需要服务端对一些请求,添加自定义的请求头。例如:统一为所有请求添加一个名为 X-Requested-Id 的请求头,用于跟踪不同请求。

Servlet 中的请求对象 jakarta.servlet.http.HttpServletRequest 并未提供设置 Header 的方法。

但是,但是通过一些方法,可以实现。本教程将会教你如何在 Spring Boot 应用中添加自定义请求头到 HttpServletRequest

思路 {#思路}

既然可以从 HttpServletRequest 获取到 Header,那它一定是把 Header 存储在了某个地方。我们只要找到 HttpServletRequest 存储 Header 的容器,就可以对其进行添加、编辑、删除操作。

不同 HttpServletRequest 的实现中,对 Header 的存储方式不一定一样。目前,在 Spring Boot 中最流行的 Servlet 实现就是 Tomcat 和 Undertow,所以下文将会针对这两个服务器进行讲解。

示例应用 {#示例应用}

创建一个 Spring Boot 应用。我们要在 Filter 中为所有请求添加一个 X-Requested-Id 请求头,并在 Controller 中获取并返回。

Controller 如下:

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

import jakarta.servlet.http.HttpServletRequest;

@RestController @RequestMapping("/demo") public class DemoController { @GetMapping public String getHeaders (HttpServletRequest request) {

    // 获取 X-Requested-Id Header 值
    String requestedId = request.getHeader("X-Requested-Id");
return requestedId;

}

}

在 Spring Boot 应用中,我们可以通过在 Handler 方法中声明 HttpServletRequest 对象,调用其 getHeader() 方法来获取客户端的请求头。

String requestedId  = request.getHeader("X-Requested-Id",);

也可以在 Handler 方法中使用 @RequestHeader 注解参数来声明要获取的请求头:

@RequestHeader(name = "X-Requested-Id", required = false) String requestedId 

Tomcat {#tomcat}

Tomcat 对 Servlet 的实现稍微有点复杂,使用了观察者模式。

  1. 首先是 org.apache.catalina.connector.RequestFacade 实现。

  2. RequestFacade 中定义了一个 org.apache.catalina.connector.Request request 的字段。

    protected org.apache.catalina.connector.Request request
    
  3. org.apache.catalina.connector.Request 中又定义了一个 org.apache.coyote.Request coyoteRequest 字段。

    protected org.apache.coyote.Request coyoteRequest;
    
  4. org.apache.coyote.Request 对象有一个 org.apache.tomcat.util.http.MimeHeaders 字段,它便是封装了客户端 Header 的容器。并且提供了获取它的 public 方法。

        // ...
        private final MimeHeaders headers = new MimeHeaders();
        // ...
        public MimeHeaders getMimeHeaders() {
            return headers;
        }
    

那么,我们可以先通过反射从 RequestFacade 中获取到 request 对象,再使用反射从 request 对象中获取到 coyoteRequest 对象。最后调用它的 getMimeHeaders() 方法来获取到封装了请求头的 MimeHeaders 对象。

Filter 实现如下:

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

import org.apache.tomcat.util.http.MimeHeaders; import org.springframework.stereotype.Component; import org.springframework.util.ReflectionUtils;

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

@WebFilter(urlPatterns = "/*") // 拦截所有请求 @Component public class TomcatRequesteIdHeaderFilter extends HttpFilter{

/**
* 
*/
private static final long serialVersionUID = -287194674100570345L;

@Override protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {

// 使用反射获取 RequestFacade 中的 org.apache.catalina.connector.Request 对象
Field connectorRequestField = ReflectionUtils.findField(req.getClass(), "request", org.apache.catalina.connector.Request.class);
connectorRequestField.setAccessible(true); // 允许反射访问
org.apache.catalina.connector.Request connectorRequest = (org.apache.catalina.connector.Request) ReflectionUtils.getField(connectorRequestField, req);

// 使用反射获取 org.apache.catalina.connector.Request 中的 org.apache.coyote.Request 对象 Field coyoteRequestField = ReflectionUtils.findField(org.apache.catalina.connector.Request.class, "coyoteRequest", org.apache.coyote.Request.class); coyoteRequestField.setAccessible(true); // 允许反射访问 org.apache.coyote.Request coyoteRequest = (org.apache.coyote.Request) ReflectionUtils.getField(coyoteRequestField, connectorRequest);

// 从 coyoteRequest 对象获取到 MimeHeaders MimeHeaders headers = coyoteRequest.getMimeHeaders();

// 添加新的 Header headers.addValue("X-Requested-Id").setString(UUID.randomUUID().toString());

// 继续执行调用链 super.doFilter(connectorRequest, res, chain);

}

}

我们在此 Filter 中为所有请求,都添加了一个 X-Requested-Id 头,它是值是一个 UUID。

测试,启动服务后使用 curl 发起请求:

$ curl localhost:8080/demo
ee3382ba-6567-4261-bb0a-91ddfbdab119

如你所见,Controller 返回了 UUID 值,表示我们成功在 HttpServletRequest 中添加了 X-Requested-Id 头。

Undertow {#undertow}

Spring Boot 默认使用 Tomcat 作为嵌入式服务器,如果你想替换为 Undertow 可以参考 这篇文章

Undertow 的实现就简单太多了,首先是 io.undertow.servlet.spec.HttpServletRequestImpl 实现。它提供了一个 public 方法可以获取到 io.undertow.server.HttpServerExchange 对象。

io.undertow.server.HttpServerExchange 又有一个 public 方法可以获取到 io.undertow.util.HeaderMap 对象,这就是封装了请求头的 Map 对象。

简而言之, Undertow 不需要反射,支持链式调用,甚至只要一行代码就可以获取到 HeaderMap 对象,如下:


import java.io.IOException;
import java.util.UUID;

import org.springframework.stereotype.Component;

import io.undertow.servlet.spec.HttpServletRequestImpl; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.http.HttpFilter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse;

@WebFilter(urlPatterns = "/*") @Component public class UndertowRequesteIdHeaderFilter extends HttpFilter{

/**
* 
*/
private static final long serialVersionUID = -374523395760767552L;

@Override protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {

// 强制转换为 HttpServletRequestImpl
HttpServletRequestImpl request = (HttpServletRequestImpl) req;
// 获取到 HeaderMap
HeaderMap headerMap = request.getExchange().getRequestHeaders();

// 添加 Header headerMap.add(HttpString.tryFromString("X-Requested-Id"), UUID.randomUUID().toString());

// 继续执行调用链 super.doFilter(req, res, chain);

}

}

Tomcat 示例一样,也是添加了一个值是 UUID 的 X-Requested-Id 头到请求中。

启动服务,使用 CURL 进行测试:

$ curl localhost:8080/demo
02ceed83-f33a-4387-8e41-e89e9916e63d

同样没问题。

赞(4)
未经允许不得转载:工具盒子 » 在 HttpServletRequest 中添加自定义请求头