当前位置: 首页 > news >正文

微服务中怎么获取请求头信息

在微服务架构中,获取 HTTP 请求头信息与在单体 Spring MVC 应用中类似,但也需要考虑一些微服务特有的场景,比如服务间的调用和上下文传播。

以下是在基于 Spring (特别是 Spring Boot / Spring Cloud) 的微服务中获取请求头信息的几种主要方式:

1. 在 Controller 层使用 @RequestHeader (最常用)

这是最直接、最标准的方式,与传统 Spring MVC 完全一致。当请求到达微服务 Controller 时,可以使用 @RequestHeader 注解将特定的请求头值注入到方法参数中。

import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;import java.util.Map;
import java.util.Optional;@RestController
public class HeaderApiController {// 1. 获取单个必需的 Header@GetMapping("/api/service-a/resource")public ResponseEntity<String> getResource(@RequestHeader("Authorization") String authorizationToken,@RequestHeader("X-Request-ID") String requestId) { // 例如,用于分布式追踪的请求 IDSystem.out.println("Authorization Token: " + authorizationToken);System.out.println("Request ID: " + requestId);// ... 处理业务逻辑 ...return ResponseEntity.ok("Resource data from Service A, Request ID: " + requestId);}// 2. 获取单个可选的 Header (带默认值)@GetMapping("/api/service-a/config")public ResponseEntity<String> getConfig(@RequestHeader(name = "X-Tenant-ID", defaultValue = "default_tenant") String tenantId) {System.out.println("Processing request for Tenant ID: " + tenantId);// ... 根据 tenantId 处理逻辑 ...return ResponseEntity.ok("Configuration for tenant: " + tenantId);}// 3. 获取单个可选的 Header (可能为 null 或使用 Optional)@GetMapping("/api/service-a/optional")public ResponseEntity<String> getOptionalData(@RequestHeader(name = "X-User-Preferences", required = false) String preferences,@RequestHeader(name = "X-Device-Type", required = false) Optional<String> deviceType) {String prefMessage = (preferences != null) ? "Preferences: " + preferences : "Preferences header missing.";String deviceMessage = deviceType.map(dt -> "Device Type: " + dt).orElse("Device Type header missing.");System.out.println(prefMessage);System.out.println(deviceMessage);return ResponseEntity.ok(prefMessage + " | " + deviceMessage);}// 4. 获取所有 Headers (推荐使用 HttpHeaders)@GetMapping("/api/service-a/all-headers")public ResponseEntity<String> getAllHeaders(@RequestHeader HttpHeaders headers) {// HttpHeaders 提供了便捷方法访问各种标准和自定义头String contentType = String.valueOf(headers.getContentType());long traceId = headers.getFirst("X-B3-TraceId") != null ? Long.parseLong(headers.getFirst("X-B3-TraceId"), 16) : -1L; // 示例:处理追踪头System.out.println("Received Headers: " + headers);System.out.println("Content-Type: " + contentType);System.out.println("Trace ID (if present): " + traceId);// 不太推荐的方式:// @RequestHeader Map<String, String> headerMap // 可能丢失多值 Header// @RequestHeader MultiValueMap<String, String> headerMultiMap // 可以处理多值 Headerreturn ResponseEntity.ok("Processed request with headers. Trace ID (long): " + traceId);}
}

2. 通过 HttpServletRequest (方法参数注入)

虽然不如 @RequestHeader 直接,但你仍然可以像在传统 Servlet 应用中那样,将 HttpServletRequest 注入到 Controller 方法中,然后调用其 getHeader(), getHeaders(), getHeaderNames() 等方法。

import javax.servlet.http.HttpServletRequest;
// ... 其他 imports ...@RestController
public class ServletHeaderController {@GetMapping("/api/service-b/info")public ResponseEntity<String> getServiceInfo(HttpServletRequest request) {String userAgent = request.getHeader("User-Agent");String customHeader = request.getHeader("X-Custom-Data");// 获取某个 header 的所有值 (如果允许重复)java.util.Enumeration<String> acceptHeaders = request.getHeaders("Accept");System.out.println("User-Agent (via HttpServletRequest): " + userAgent);System.out.println("X-Custom-Data (via HttpServletRequest): " + customHeader);StringBuilder acceptHeaderString = new StringBuilder();if (acceptHeaders != null) {while(acceptHeaders.hasMoreElements()) {acceptHeaderString.append(acceptHeaders.nextElement()).append(", ");}}System.out.println("Accept Headers: " + acceptHeaderString);// ... 业务逻辑 ...return ResponseEntity.ok("Info from Service B. User-Agent starts with: " + (userAgent != null ? userAgent.substring(0, 10) : "N/A"));}
}

何时使用? 当需要访问 @RequestHeader 不直接支持的功能,或者需要更底层的控制时(虽然比较少见)。

3. 在过滤器 (Filter) 或拦截器 (Interceptor) 中获取

在微服务中,很多横切关注点(Cross-Cutting Concerns)如认证、授权、日志记录、分布式追踪上下文传播等,在请求到达 Controller 之前处理。这时,可以在 FilterInterceptor 中获取请求头。

示例 (使用 Servlet Filter):

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC; // 用于日志上下文
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; // 保证每个请求只执行一次import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;@Component // 让 Spring 管理这个 Filter
public class LoggingAndTraceFilter extends OncePerRequestFilter {private static final Logger log = LoggerFactory.getLogger(LoggingAndTraceFilter.class);private static final String TRACE_ID_HEADER = "X-Trace-ID";private static final String TENANT_ID_HEADER = "X-Tenant-ID";private static final String TRACE_ID_MDC_KEY = "traceId";private static final String TENANT_ID_MDC_KEY = "tenantId";@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {// 1. 从请求头获取或生成 Trace IDString traceId = request.getHeader(TRACE_ID_HEADER);if (traceId == null || traceId.isEmpty()) {traceId = UUID.randomUUID().toString();log.debug("Generated new Trace ID: {}", traceId);// 如果需要传播,可将生成的 ID 添加到响应头或下游请求头}// 2. 从请求头获取 Tenant IDString tenantId = request.getHeader(TENANT_ID_HEADER);if (tenantId == null) {tenantId = "unknown"; // Or handle as an error depending on requirements}// 3. 将信息放入 MDC (Mapped Diagnostic Context) 以便日志自动包含这些信息MDC.put(TRACE_ID_MDC_KEY, traceId);MDC.put(TENANT_ID_MDC_KEY, tenantId);log.info("Request received: {} {} for Tenant: {}", request.getMethod(), request.getRequestURI(), tenantId);try {// 继续处理请求链 (最终会到达 Controller)filterChain.doFilter(request, response);} finally {// 4. 请求处理完毕后,清理 MDC,避免影响线程池中的下一个请求MDC.remove(TRACE_ID_MDC_KEY);MDC.remove(TENANT_ID_MDC_KEY);log.info("Request completed: {} {}", request.getMethod(), request.getRequestURI());}}
}

场景:

  • 分布式追踪: 获取或生成追踪 ID (如 X-Request-ID, X-B3-TraceId),放入日志上下文 (MDC),并在调用下游服务时传递。Spring Cloud Sleuth 等库会自动处理很多这类逻辑。
  • 认证/授权: 读取 Authorization 头,验证 Token,并将用户信息放入安全上下文 (SecurityContextHolder)。Spring Security 等框架会做这些。
  • 多租户: 读取 X-Tenant-ID 头,设置当前线程的租户上下文。
  • 通用日志: 记录所有请求的某些通用头信息。

4. 在 Service 层或更深层访问 (需要传递或使用上下文)

如果业务逻辑层 (Service Layer) 或其他非 Controller 组件需要访问请求头信息,不推荐直接依赖 HttpServletRequestRequestContextHolder (因为它会耦合业务逻辑与 Web 层)。

推荐的方式是:

  • 参数传递 (首选): 在 Controller 层使用 @RequestHeader 获取所需头信息,然后将其作为参数传递给 Service 方法。这是最清晰、最解耦、最易于测试的方式。

    // Controller
    @GetMapping("/api/service-c/process")
    public ResponseEntity<String> processData(@RequestHeader("X-User-ID") String userId,@RequestHeader("X-Correlation-ID") String correlationId) {String result = myService.processUserData(userId, correlationId); // 传递给 Servicereturn ResponseEntity.ok(result);
    }// Service
    @Service
    public class MyService {public String processUserData(String userId, String correlationId) {// 使用 userId 和 correlationId 进行处理System.out.println("Service processing for User: " + userId + ", Correlation: " + correlationId);// ...return "Processed data for user " + userId;}
    }
    
  • 使用线程本地上下文 (ThreadLocal): 对于像追踪 ID、租户 ID、用户 ID 这样需要在整个请求处理链中都可用的信息,可以在 Filter/Interceptor 中将其读取并存入自定义的 ThreadLocal 变量或使用现成的上下文机制(如 Spring Security 的 SecurityContextHolder、日志的 MDC、或自定义的 TenantContextHolder)。然后在 Service 层或其他地方从该上下文中读取。

    // 在 Filter/Interceptor 中设置 (如上面的 LoggingAndTraceFilter 示例设置 MDC)// 在 Service 层获取
    @Service
    public class AnotherService {private static final Logger log = LoggerFactory.getLogger(AnotherService.class);public void doSomething() {String traceId = MDC.get("traceId"); // 从 MDC 获取String tenantId = MDC.get("tenantId");// 或者从 SecurityContextHolder 获取用户信息// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();// String username = (authentication != null) ? authentication.getName() : "N/A";// 使用这些上下文信息log.info("Doing something within service context. Trace ID: {}, Tenant ID: {}", traceId, tenantId);// ...}
    }
    

总结与建议

  1. Controller 层: 优先使用 @RequestHeader。清晰、简洁,是 Spring 推荐的方式。使用 HttpHeaders 参数可以获取所有头信息。
  2. Filter/Interceptor: 用于处理横切关注点,如日志、认证、追踪、多租户等,获取相关头信息,并可以设置请求/线程上下文(如 MDC, SecurityContext, 自定义 ThreadLocal)。
  3. Service 层及以下: 优先通过方法参数传递从 Controller 获取的头信息。如果信息是全局上下文(如追踪 ID、用户 ID、租户 ID),则通过 Filter/Interceptor 设置并在需要时从线程本地上下文(MDC, SecurityContextHolder, 自定义 ContextHolder)读取。避免直接依赖 Web 层 API
  4. 框架利用: 充分利用 Spring Cloud Sleuth (用于追踪头传播)、Spring Security (用于认证头处理) 等框架,它们能自动处理很多常见的头信息获取和上下文管理工作。

根据具体需求选择最合适的方式。在微服务中,清晰的处理和传递请求头对于保证服务的正确性、可观测性和安全性至关重要。

相关文章:

  • WSL 安装 Debian 后,apt get 如何更改到国内镜像网址?
  • Python从入门到高手8.2节-元组的常用操作符
  • R004 -计算机硬件基础
  • Matlab实现基于CNN-GRU的锂电池SOH估计
  • Vite简单介绍
  • 五一作业-day03
  • Baklib知识中台:智能服务架构新实践
  • Java求职面试:Spring Boot与微服务的幽默探讨
  • 【Hive入门】Hive安全管理与权限控制:用户认证与权限管理深度解析
  • 代码随想录算法训练营第三十二天
  • MATLAB人工大猩猩部队GTO优化CNN-LSTM多变量时间序列预测
  • 文本三剑客试题
  • Android设备运行yolov8
  • 系统架构-层次式架构设计
  • MATLAB仿真定点数转浮点数(对比VIVADO定点转浮点)
  • 【PostgreSQL数据分析实战:从数据清洗到可视化全流程】5.2 数据分组与透视(CUBE/ROLLUP/GROUPING SETS)
  • 【PostgreSQL数据分析实战:从数据清洗到可视化全流程】4.5 清洗流程自动化(存储过程/定时任务)
  • 谷歌最新推出的Gemini 2.5 Flash人工智能模型因其安全性能相较前代产品出现下滑
  • word导出pdf带有目录导航栏-error记
  • Redis:现代服务端开发的缓存基石与电商实践-优雅草卓伊凡
  • 谢晖不再担任中超长春亚泰队主教练:战绩不佳主动请辞
  • 安赛乐米塔尔深化在华战略布局,VAMA总经理:做中国汽车板竞争力前三
  • 哈马斯官员:进一步停火谈判毫无意义
  • 蓝佛安主持东盟与中日韩财长和央行行长系列会议并举行多场双边会见
  • 黎巴嫩9年来首次举行地方选举
  • 专访|刘伟强:在《水饺皇后》里,我放进儿时全家福照片