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

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

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

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

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

java 复制代码
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) { // 例如,用于分布式追踪的请求 ID

        System.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 // 可以处理多值 Header

        return ResponseEntity.ok("Processed request with headers. Trace ID (long): " + traceId);
    }
}

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

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

java 复制代码
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):

java 复制代码
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";


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        // 1. 从请求头获取或生成 Trace ID
        String 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 ID
        String 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 方法。这是最清晰、最解耦、最易于测试的方式。

    java 复制代码
    // 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); // 传递给 Service
        return 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 层或其他地方从该上下文中读取。

    java 复制代码
    // 在 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 (用于认证头处理) 等框架,它们能自动处理很多常见的头信息获取和上下文管理工作。

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

相关推荐
前端熊猫2 小时前
公司项目架构搭建者
架构
就叫飞六吧8 小时前
Kubernetes弹性伸缩:让应用自动应对流量洪峰与低谷
云原生·容器·kubernetes
珊珊而川9 小时前
5.1经典架构
架构
Lw老王要学习13 小时前
25_05_02Linux架构篇、第1章_03安装部署nginx
linux·运维·nginx·架构·云计算·it
yibuapi_com14 小时前
开源智能体MetaGPT记忆模块解读
python·ai·语言模型·chatgpt·架构·langchain·claude
互联网搬砖老肖15 小时前
深入理解 Web 架构:从基础到实践
前端·架构
大米74516 小时前
kubeadm部署k8s
云原生·容器·kubernetes
玄武后端技术栈17 小时前
说下RabbitMQ的整体架构
分布式·架构·rabbitmq
国际云,接待18 小时前
2025年服务器技术全景解析:量子计算、液冷革命与未来生态构建
运维·服务器·架构·云计算·腾讯云·量子计算