微服务中怎么获取请求头信息
在微服务架构中,获取 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 之前处理。这时,可以在 Filter
或 Interceptor
中获取请求头。
示例 (使用 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 组件需要访问请求头信息,不推荐直接依赖 HttpServletRequest
或 RequestContextHolder
(因为它会耦合业务逻辑与 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);// ...} }
总结与建议
- Controller 层: 优先使用
@RequestHeader
。清晰、简洁,是 Spring 推荐的方式。使用HttpHeaders
参数可以获取所有头信息。 - Filter/Interceptor: 用于处理横切关注点,如日志、认证、追踪、多租户等,获取相关头信息,并可以设置请求/线程上下文(如 MDC, SecurityContext, 自定义 ThreadLocal)。
- Service 层及以下: 优先通过方法参数传递从 Controller 获取的头信息。如果信息是全局上下文(如追踪 ID、用户 ID、租户 ID),则通过 Filter/Interceptor 设置并在需要时从线程本地上下文(MDC, SecurityContextHolder, 自定义 ContextHolder)读取。避免直接依赖 Web 层 API。
- 框架利用: 充分利用 Spring Cloud Sleuth (用于追踪头传播)、Spring Security (用于认证头处理) 等框架,它们能自动处理很多常见的头信息获取和上下文管理工作。
根据具体需求选择最合适的方式。在微服务中,清晰的处理和传递请求头对于保证服务的正确性、可观测性和安全性至关重要。