对HttpServletRequest中的Header进行增删

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获取响应结果

image

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

SpringBoot默认的Servlet容器就是Tomcat

使用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获取结果

image

最后

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


原文:https://springboot.io/t/topic/3643

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容