写点什么

对 HttpServletRequest 中的 Header 进行增删

作者:派大星
  • 2024-09-23
    辽宁
  • 本文字数:4642 字

    阅读完需:约 15 分钟

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


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


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

发布于: 刚刚阅读数: 4
用户头像

派大星

关注

微信搜索【码上遇见你】,获取更多精彩内容 2021-12-13 加入

微信搜索【码上遇见你】,获取更多精彩内容

评论

发布
暂无评论
对HttpServletRequest中的Header进行增删_HttpServletRequest_派大星_InfoQ写作社区