對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 只是一個接口我纪,實現(xiàn)由Servlet容器提供。不管是任何容器,實現(xiàn)類,肯定是要把請求的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的實現(xiàn)

Tomcat使用了外觀模式(Facade),這個實現(xiàn)稍微有一點點復雜

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 實現(xiàn)液荸,它有一個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統(tǒng)一添加一個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容器然遏,據(jù)說性能比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實現(xiàn)

它的實現(xiàn)就比較簡單

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

io.undertow.servlet.spec.HttpServletRequestImpl實現(xiàn)類中有一個屬性對象io.undertow.server.HttpServerExchange吧彪,這個屬性對象又包含了一個io.undertow.util.HeaderMap待侵,HeaderMap就是請求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。只要熟悉基本的反射傀缩,實現(xiàn)對請求頭的增刪那先,都很簡單。


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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末赡艰,一起剝皮案震驚了整個濱河市售淡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌慷垮,老刑警劉巖揖闸,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異料身,居然都是意外死亡汤纸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門芹血,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贮泞,“玉大人,你說我怎么就攤上這事祟牲∠缎螅” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵说贝,是天一觀的道長议惰。 經(jīng)常有香客問我,道長乡恕,這世上最難降的妖魔是什么言询? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮傲宜,結果婚禮上运杭,老公的妹妹穿的比我還像新娘。我一直安慰自己函卒,他們只是感情好辆憔,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般虱咧。 火紅的嫁衣襯著肌膚如雪熊榛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天腕巡,我揣著相機與錄音玄坦,去河邊找鬼。 笑死绘沉,一個胖子當著我的面吹牛煎楣,可吹牛的內容都是我干的。 我是一名探鬼主播车伞,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼择懂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了帖世?” 一聲冷哼從身側響起休蟹,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎日矫,沒想到半個月后赂弓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡哪轿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年盈魁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窃诉。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡杨耙,死狀恐怖,靈堂內的尸體忽然破棺而出飘痛,到底是詐尸還是另有隱情珊膜,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布宣脉,位于F島的核電站车柠,受9級特大地震影響,放射性物質發(fā)生泄漏塑猖。R本人自食惡果不足惜竹祷,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望羊苟。 院中可真熱鬧塑陵,春花似錦、人聲如沸蜡励。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至兼都,卻和暖如春鳍刷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俯抖。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瓦胎,地道東北人芬萍。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像搔啊,于是被迫代替她去往敵國和親柬祠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容