在Spring中給HttpServletRequest添加自定義Header的完整指南
引言
在 Spring 應(yīng)用開發(fā)中,我們經(jīng)常需要在請(qǐng)求處理過(guò)程中添加自定義的 HTTP Header。由于 HttpServletRequest 對(duì)象在設(shè)計(jì)上是只讀的,直接添加 Header 會(huì)遇到困難。本文將詳細(xì)介紹如何通過(guò) Filter 和 RequestWrapper 的方式優(yōu)雅地解決這個(gè)問(wèn)題。
問(wèn)題背景
HttpServletRequest 接口本身不提供修改 Header 的方法,這是因?yàn)椋?/p>
- Servlet 規(guī)范設(shè)計(jì)請(qǐng)求對(duì)象為只讀
- 保證請(qǐng)求數(shù)據(jù)的完整性和一致性
- 避免在多線程環(huán)境下出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)
但在實(shí)際業(yè)務(wù)場(chǎng)景中,我們經(jīng)常需要添加一些業(yè)務(wù)相關(guān)的 Header或引用的包中會(huì)依賴header中的某個(gè)值,比如:
- 租戶 ID(tenantId)
- 用戶追蹤信息
- 認(rèn)證令牌
- 業(yè)務(wù)上下文信息
解決方案:Filter + RequestWrapper
核心思路
通過(guò)創(chuàng)建 HttpServletRequestWrapper 子類來(lái)包裝原始請(qǐng)求,重寫相關(guān)方法以支持自定義 Header 的添加。
完整代碼實(shí)現(xiàn)
1. 自定義 RequestWrapper
public class RequestWrapper extends HttpServletRequestWrapper {
// 可選,如需對(duì)body數(shù)據(jù)做處理
private String mBody;
private Map<String, String> headerMap = new HashMap<>();
public RequestWrapper(HttpServletRequest request) {
super(request);
mBody = getBodyString(request);
}
/**
* 添加自定義 Header
*/
public void addHeader(String name, String value) {
headerMap.put(name, value);
}
@Override
public String getHeader(String name) {
String headerValue = super.getHeader(name);
if (headerMap.containsKey(name)) {
headerValue = headerMap.get(name);
}
return headerValue;
}
@Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
names.addAll(headerMap.keySet());
return Collections.enumeration(names);
}
@Override
public Enumeration<String> getHeaders(String name) {
List<String> values = Collections.list(super.getHeaders(name));
if (headerMap.containsKey(name)) {
values.add(headerMap.get(name));
}
return Collections.enumeration(values);
}
/**
* 獲取請(qǐng)求體內(nèi)容
*/
private String getBodyString(ServletRequest request) {
StringBuilder sb = new StringBuilder();
try (InputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
logger.error("讀取請(qǐng)求體失敗", e);
}
return sb.toString();
}
// 其他必要的方法重寫...
}
2. 實(shí)現(xiàn)自定義 Filter
@Component
@Order(10000)
@WebFilter(filterName = "HttpServletRequestFilter", urlPatterns = "/*")
public class HttpServletRequestFilter implements Filter {
private static final Set<String> EXCLUDE_PATHS = Set.of(
"/health", "/actuator/health", "/favicon.ico"
);
private static final Logger logger = LoggerFactory.getLogger(HttpServletRequestFilter.class);
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
String path = httpRequest.getRequestURI();
// 跳過(guò)不需要處理的路徑
if (shouldSkipProcessing(path)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
// 包裝請(qǐng)求
RequestWrapper requestWrapper = new RequestWrapper(httpRequest);
// 設(shè)置自定義 Header,這里可以在別處設(shè)置,比如從token中解析后調(diào)用setCustomHeaders
setCustomHeaders(requestWrapper);
// 繼續(xù)過(guò)濾器鏈
filterChain.doFilter(requestWrapper, servletResponse);
}
/**
* 判斷是否跳過(guò)處理
*/
private boolean shouldSkipProcessing(String path) {
return EXCLUDE_PATHS.stream().anyMatch(path::startsWith);
}
/**
* 設(shè)置自定義 Header
*/
private void setCustomHeaders(HttpServletRequest request) {
try {
// 示例:從用戶上下文中獲取租戶ID
User user = UserContext.getUser();
if (user != null && user.getTenantId() != null) {
setHeaderTenantId(request, user.getTenantId());
}
// 可以添加更多自定義 Header
// setOtherHeaders(request);
} catch (Exception e) {
logger.warn("設(shè)置自定義 Header 失敗", e);
}
}
/**
* 設(shè)置租戶ID Header
*/
private void setHeaderTenantId(HttpServletRequest request, String tenantId) {
if (request instanceof StandardMultipartHttpServletRequest) {
// 處理文件上傳請(qǐng)求
StandardMultipartHttpServletRequest multipartRequest =
(StandardMultipartHttpServletRequest) request;
HttpServletRequest originalRequest = multipartRequest.getRequest();
if (originalRequest instanceof RequestWrapper) {
((RequestWrapper) originalRequest).addHeader("tenantId", tenantId);
}
} else if (request instanceof RequestWrapper) {
// 處理普通請(qǐng)求
((RequestWrapper) request).addHeader("tenantId", tenantId);
}
// 其他類型的請(qǐng)求可以根據(jù)需要擴(kuò)展
}
@Override
public void init(FilterConfig filterConfig) {
logger.info("HttpServletRequestFilter 初始化完成");
}
@Override
public void destroy() {
logger.info("HttpServletRequestFilter 銷毀");
}
}
關(guān)鍵技術(shù)點(diǎn)解析
1. HttpServletRequestWrapper 的作用
HttpServletRequestWrapper 是裝飾器模式的實(shí)現(xiàn),它允許我們:
- 包裝原始請(qǐng)求對(duì)象
- 選擇性重寫方法
- 保持其他方法的默認(rèn)行為
2. 必須重寫的方法
要支持自定義 Header,必須重寫以下三個(gè)方法:
getHeader(String name)- 獲取單個(gè) Header 值getHeaders(String name)- 獲取 Header 的所有值getHeaderNames()- 獲取所有 Header 名稱
3. 請(qǐng)求體處理
由于 HTTP 請(qǐng)求的輸入流只能讀取一次,我們需要在包裝時(shí)保存請(qǐng)求體內(nèi)容:
private String getBodyString(ServletRequest request) {
// 讀取并保存請(qǐng)求體,支持重復(fù)讀取
}
4. 特殊請(qǐng)求類型處理
對(duì)于 MultipartHttpServletRequest(文件上傳)等特殊請(qǐng)求類型,需要特殊處理:
if (request instanceof StandardMultipartHttpServletRequest) {
// 獲取底層請(qǐng)求對(duì)象進(jìn)行處理
}
使用示例
在 Controller 中獲取自定義 Header:
@RestController
public class TestController {
@GetMapping("/api/data")
public ResponseEntity<?> getData(HttpServletRequest request) {
// 獲取自定義的 tenantId Header
String tenantId = request.getHeader("tenantId");
// 使用 tenantId 進(jìn)行業(yè)務(wù)處理
List<Data> result = dataService.getDataByTenant(tenantId);
return ResponseEntity.ok(result);
}
}
最佳實(shí)踐建議
1. 性能優(yōu)化
- 路徑排除:對(duì)健康檢查等不需要處理的路徑進(jìn)行排除
- 懶加載:只在需要時(shí)讀取請(qǐng)求體
- 資源清理:及時(shí)關(guān)閉流和清理 ThreadLocal
2. 錯(cuò)誤處理
- 異常捕獲:妥善處理可能出現(xiàn)的異常
- 降級(jí)策略:在無(wú)法設(shè)置 Header 時(shí)提供合理的降級(jí)方案
- 日志記錄:記錄關(guān)鍵操作和錯(cuò)誤信息
3. 可維護(hù)性
- 配置化:將排除路徑等配置外部化
- 模塊化:將 Header 設(shè)置邏輯分離到獨(dú)立的服務(wù)中
- 文檔化:記錄自定義 Header 的含義和用途
替代方案比較
1. ThreadLocal 方式
優(yōu)點(diǎn):簡(jiǎn)單高效
缺點(diǎn):需要在同一線程內(nèi)傳遞,對(duì)異步支持不友好
2. Request Attribute 方式
優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單
缺點(diǎn):不符合 HTTP Header 的語(yǔ)義
3. Filter + Wrapper 方式(本文方案)
優(yōu)點(diǎn):符合 Servlet 規(guī)范,支持所有請(qǐng)求類型
缺點(diǎn):實(shí)現(xiàn)相對(duì)復(fù)雜
總結(jié)
通過(guò) Filter 和 HttpServletRequestWrapper 的組合,我們可以優(yōu)雅地在 Spring 應(yīng)用中添加自定義 HTTP Header。這種方案:
- 符合 Servlet 規(guī)范,不破壞現(xiàn)有框架行為
- 支持所有請(qǐng)求類型,包括文件上傳等特殊場(chǎng)景
- 具有良好的擴(kuò)展性,可以方便地添加更多自定義 Header
- 保持代碼整潔,業(yè)務(wù)代碼無(wú)需關(guān)心 Header 的設(shè)置細(xì)節(jié)
在實(shí)際項(xiàng)目中,建議根據(jù)具體需求選擇合適的方案,并遵循本文提到的最佳實(shí)踐,以確保代碼的健壯性和可維護(hù)性。
以上就是在Spring中給HttpServletRequest添加自定義Header的完整指南的詳細(xì)內(nèi)容,更多關(guān)于Spring給HttpServletRequest添加Header的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
MyBatis?多表聯(lián)合查詢及優(yōu)化方法
大家都知道Hibernate 是全自動(dòng)的數(shù)據(jù)庫(kù)持久層框架,它可以通過(guò)實(shí)體來(lái)映射數(shù)據(jù)庫(kù),通過(guò)設(shè)置一對(duì)多、多對(duì)一、一對(duì)一、多對(duì)多的關(guān)聯(lián)來(lái)實(shí)現(xiàn)聯(lián)合查詢,接下來(lái)通過(guò)本文給大家介紹MyBatis?多表聯(lián)合查詢及優(yōu)化,需要的朋友可以參考下2022-08-08
Spring Boot 整合 Druid 并開啟監(jiān)控的操作方法
本文介紹了如何在SpringBoot項(xiàng)目中引入和配置Druid數(shù)據(jù)庫(kù)連接池,并開啟其監(jiān)控功能,通過(guò)添加依賴、配置數(shù)據(jù)源、開啟監(jiān)控、自定義配置以及訪問(wèn)監(jiān)控頁(yè)面,開發(fā)者可以有效提高數(shù)據(jù)庫(kù)訪問(wèn)效率并監(jiān)控連接池狀態(tài),感興趣的朋友跟隨小編一起看看吧2025-01-01
spring boot如何基于JWT實(shí)現(xiàn)單點(diǎn)登錄詳解
這篇文章主要介紹了spring boot如何基于JWT實(shí)現(xiàn)單點(diǎn)登錄詳解,用戶只需登錄一次就能夠在這兩個(gè)系統(tǒng)中進(jìn)行操作。很明顯這就是單點(diǎn)登錄(Single Sign-On)達(dá)到的效果,需要的朋友可以參考下2019-06-06

