Skip to content

Spring WebFlux @RequestHeader 注解详解 🚀

什么是 @RequestHeader?

@RequestHeader 是 Spring WebFlux 中用于绑定 HTTP 请求头到控制器方法参数的注解。它让我们能够轻松地从客户端请求中提取和使用各种头部信息。

NOTE

在 Web 开发中,HTTP 头部承载着丰富的元数据信息,如客户端类型、编码格式、认证信息等。@RequestHeader 让我们能够优雅地访问这些关键信息。

为什么需要 @RequestHeader? 🤔

解决的核心痛点

在没有 @RequestHeader 之前,开发者需要手动从 ServerHttpRequest 对象中提取头部信息,这种方式存在以下问题:

kotlin
@GetMapping("/demo")
fun handleOldWay(request: ServerHttpRequest): Mono<String> {
    // 手动提取头部信息,代码冗长
    val headers = request.headers
    val encoding = headers.getFirst("Accept-Encoding") ?: "default"
    val keepAlive = headers.getFirst("Keep-Alive")?.toLongOrNull() ?: 0L
    
    // 需要手动处理空值和类型转换
    return Mono.just("Processed with encoding: $encoding, keepAlive: $keepAlive")
}
kotlin
@GetMapping("/demo")
fun handle(
    @RequestHeader("Accept-Encoding") encoding: String, 
    @RequestHeader("Keep-Alive") keepAlive: Long
): Mono<String> {
    // 直接使用,Spring 自动处理类型转换和绑定
    return Mono.just("Processed with encoding: $encoding, keepAlive: $keepAlive")
}

HTTP 请求头基础知识 📋

让我们先了解一个典型的 HTTP 请求头结构:

基础用法示例 💡

1. 单个头部绑定

kotlin
@RestController
class HeaderController {

    @GetMapping("/basic-header")
    fun handleBasicHeader(
        @RequestHeader("User-Agent") userAgent: String
    ): Mono<Map<String, Any>> {
        return Mono.just(mapOf(
            "message" to "请求处理成功",
            "userAgent" to userAgent,
            "timestamp" to System.currentTimeMillis()
        ))
    }
}

2. 多个头部同时绑定

kotlin
@GetMapping("/multiple-headers")
fun handleMultipleHeaders(
    @RequestHeader("Accept-Encoding") encoding: String, 
    @RequestHeader("Keep-Alive") keepAlive: Long, 
    @RequestHeader("Accept-Language") language: String
): Mono<ResponseEntity<Map<String, Any>>> {
    
    val responseData = mapOf(
        "encoding" to encoding,
        "keepAlive" to keepAlive,
        "language" to language,
        "processed" to true
    )
    
    return Mono.just(ResponseEntity.ok(responseData))
}

高级特性详解 🔥

1. 自动类型转换

Spring WebFlux 提供强大的类型转换能力:

kotlin
@GetMapping("/type-conversion")
fun handleTypeConversion(
    @RequestHeader("Content-Length") contentLength: Int, 
    @RequestHeader("Keep-Alive") keepAlive: Long, 
    @RequestHeader("X-Custom-Flag") customFlag: Boolean, 
    @RequestHeader("X-Rate-Limit") rateLimit: Double
): Mono<String> {
    return Mono.just("""
        Content Length: $contentLength (Int)
        Keep Alive: $keepAlive (Long)  
        Custom Flag: $customFlag (Boolean)
        Rate Limit: $rateLimit (Double)
    """.trimIndent())
}

TIP

Spring 会自动将字符串类型的头部值转换为目标参数类型。如果转换失败,会抛出类型转换异常。

2. 可选头部处理

kotlin
@GetMapping("/optional-headers")
fun handleOptionalHeaders(
    @RequestHeader(value = "Authorization", required = false) 
    authorization: String?, 
    
    @RequestHeader(value = "X-API-Version", defaultValue = "v1") 
    apiVersion: String
): Mono<Map<String, Any?>> {
    
    return Mono.just(mapOf(
        "isAuthenticated" to (authorization != null),
        "apiVersion" to apiVersion,
        "authHeader" to authorization
    ))
}

3. 集合类型头部

处理包含多个值的头部:

kotlin
@GetMapping("/collection-headers")
fun handleCollectionHeaders(
    @RequestHeader("Accept") acceptTypes: List<String>, 
    @RequestHeader("Accept-Language") languages: Array<String> 
): Mono<Map<String, Any>> {
    
    return Mono.just(mapOf(
        "acceptTypes" to acceptTypes,
        "languages" to languages.toList(),
        "primaryLanguage" to languages.firstOrNull()
    ))
}

4. 获取所有头部信息

kotlin
@GetMapping("/all-headers-map")
fun handleAllHeadersAsMap(
    @RequestHeader headers: Map<String, String> 
): Mono<Map<String, Any>> {
    
    return Mono.just(mapOf(
        "totalHeaders" to headers.size,
        "headers" to headers
    ))
}
kotlin
@GetMapping("/all-headers-multivalue")
fun handleAllHeadersAsMultiValue(
    @RequestHeader headers: MultiValueMap<String, String> 
): Mono<Map<String, Any>> {
    
    val processedHeaders = headers.mapValues { (_, values) ->
        if (values.size == 1) values.first() else values
    }
    
    return Mono.just(mapOf(
        "totalHeaders" to headers.size,
        "headers" to processedHeaders
    ))
}
kotlin
@GetMapping("/all-headers-http")
fun handleAllHeadersAsHttpHeaders(
    @RequestHeader headers: HttpHeaders
): Mono<Map<String, Any>> {
    
    return Mono.just(mapOf(
        "contentType" to headers.contentType?.toString(),
        "contentLength" to headers.contentLength,
        "host" to headers.host?.toString(),
        "allHeaders" to headers.toSingleValueMap()
    ))
}

实际业务场景应用 🏢

1. API 版本控制

kotlin
@RestController
class VersionedApiController {

    @GetMapping("/api/users")
    fun getUsers(
        @RequestHeader(value = "API-Version", defaultValue = "v1") 
        apiVersion: String
    ): Mono<ResponseEntity<List<Map<String, Any>>>> {
        
        return when (apiVersion) {
            "v1" -> getUsersV1()
            "v2" -> getUsersV2()
            else -> Mono.just(ResponseEntity.badRequest().build())
        }
    }
    
    private fun getUsersV1(): Mono<ResponseEntity<List<Map<String, Any>>>> {
        val users = listOf(
            mapOf("id" to 1, "name" to "张三"),
            mapOf("id" to 2, "name" to "李四")
        )
        return Mono.just(ResponseEntity.ok(users))
    }
    
    private fun getUsersV2(): Mono<ResponseEntity<List<Map<String, Any>>>> {
        val users = listOf(
            mapOf("id" to 1, "name" to "张三", "email" to "[email protected]"),
            mapOf("id" to 2, "name" to "李四", "email" to "[email protected]")
        )
        return Mono.just(ResponseEntity.ok(users))
    }
}

2. 认证和授权

kotlin
@RestController
class SecureController {

    @GetMapping("/secure/profile")
    fun getUserProfile(
        @RequestHeader("Authorization") authorization: String
    ): Mono<ResponseEntity<Map<String, Any>>> {
        
        return validateToken(authorization)
            .flatMap { userId ->
                getUserById(userId)
            }
            .map { user ->
                ResponseEntity.ok(user)
            }
            .onErrorReturn(ResponseEntity.status(HttpStatus.UNAUTHORIZED).build())
    }
    
    private fun validateToken(authorization: String): Mono<String> {
        return if (authorization.startsWith("Bearer ")) {
            val token = authorization.substring(7)
            // 模拟 token 验证逻辑
            if (token == "valid-token-123") {
                Mono.just("user123")
            } else {
                Mono.error(RuntimeException("Invalid token"))
            }
        } else {
            Mono.error(RuntimeException("Invalid authorization format"))
        }
    }
    
    private fun getUserById(userId: String): Mono<Map<String, Any>> {
        return Mono.just(mapOf(
            "id" to userId,
            "name" to "用户名",
            "email" to "[email protected]"
        ))
    }
}

3. 国际化支持

kotlin
@RestController
class I18nController {

    @GetMapping("/welcome")
    fun getWelcomeMessage(
        @RequestHeader(value = "Accept-Language", defaultValue = "zh-CN") 
        acceptLanguage: String
    ): Mono<Map<String, String>> {
        
        val language = parseLanguage(acceptLanguage)
        val message = getLocalizedMessage(language)
        
        return Mono.just(mapOf(
            "message" to message,
            "language" to language
        ))
    }
    
    private fun parseLanguage(acceptLanguage: String): String {
        // 解析 Accept-Language 头部,如 "zh-CN,zh;q=0.9,en;q=0.8"
        return acceptLanguage.split(",")
            .firstOrNull()
            ?.split(";")
            ?.firstOrNull()
            ?.trim() ?: "zh-CN"
    }
    
    private fun getLocalizedMessage(language: String): String {
        return when (language.lowercase()) {
            "zh-cn", "zh" -> "欢迎使用我们的服务!"
            "en", "en-us" -> "Welcome to our service!"
            "ja" -> "私たちのサービスへようこそ!"
            else -> "欢迎使用我们的服务!"
        }
    }
}

异常处理和最佳实践 ⚠️

1. 头部缺失处理

kotlin
@RestController
class RobustHeaderController {

    @GetMapping("/robust-headers")
    fun handleRobustHeaders(
        @RequestHeader(value = "X-Client-ID", required = false) 
        clientId: String?,
        
        @RequestHeader(value = "X-Request-ID", defaultValue = "") 
        requestId: String
    ): Mono<ResponseEntity<Map<String, Any>>> {
        
        // 生成默认的请求 ID
        val finalRequestId = if (requestId.isBlank()) {
            UUID.randomUUID().toString()
        } else {
            requestId
        }
        
        val response = mapOf(
            "clientId" to (clientId ?: "anonymous"),
            "requestId" to finalRequestId,
            "timestamp" to System.currentTimeMillis()
        )
        
        return Mono.just(ResponseEntity.ok(response))
    }
}

2. 全局异常处理

kotlin
@RestControllerAdvice
class HeaderExceptionHandler {

    @ExceptionHandler(MissingRequestHeaderException::class) 
    fun handleMissingHeader(ex: MissingRequestHeaderException): ResponseEntity<Map<String, Any>> {
        val error = mapOf(
            "error" to "MISSING_HEADER",
            "message" to "缺少必需的请求头: ${ex.headerName}",
            "timestamp" to System.currentTimeMillis()
        )
        return ResponseEntity.badRequest().body(error)
    }
    
    @ExceptionHandler(TypeMismatchException::class) 
    fun handleTypeMismatch(ex: TypeMismatchException): ResponseEntity<Map<String, Any>> {
        val error = mapOf(
            "error" to "TYPE_CONVERSION_ERROR",
            "message" to "请求头类型转换失败: ${ex.propertyName}",
            "timestamp" to System.currentTimeMillis()
        )
        return ResponseEntity.badRequest().body(error)
    }
}

性能优化建议 🚀

1. 避免过度使用 Map 接收所有头部

WARNING

当使用 @RequestHeader Map<String, String> headers 时,Spring 会解析所有请求头,这在头部信息很多时可能影响性能。

kotlin
// ❌ 不推荐:解析所有头部但只使用少数几个
@GetMapping("/inefficient")
fun inefficientWay(@RequestHeader headers: Map<String, String>): Mono<String> {
    val userAgent = headers["User-Agent"] 
    val contentType = headers["Content-Type"] 
    return Mono.just("Processed")
}

// ✅ 推荐:只绑定需要的头部
@GetMapping("/efficient")
fun efficientWay(
    @RequestHeader("User-Agent") userAgent: String, 
    @RequestHeader("Content-Type") contentType: String
): Mono<String> {
    return Mono.just("Processed")
}

2. 合理使用默认值

kotlin
@GetMapping("/optimized")
fun optimizedHeaders(
    @RequestHeader(value = "X-Rate-Limit", defaultValue = "100") 
    rateLimit: Int,
    
    @RequestHeader(value = "X-Timeout", defaultValue = "30000") 
    timeout: Long
): Mono<String> {
    // 避免了空值检查,提高代码执行效率
    return Mono.just("Rate limit: $rateLimit, Timeout: $timeout")
}

测试示例 🧪

完整的测试用例示例
kotlin
@WebFluxTest(HeaderController::class)
class HeaderControllerTest {

    @Autowired
    private lateinit var webTestClient: WebTestClient

    @Test
    fun `should handle basic header correctly`() {
        webTestClient.get()
            .uri("/basic-header")
            .header("User-Agent", "Mozilla/5.0") 
            .exchange()
            .expectStatus().isOk
            .expectBody()
            .jsonPath("$.userAgent").isEqualTo("Mozilla/5.0")
    }

    @Test
    fun `should handle multiple headers`() {
        webTestClient.get()
            .uri("/multiple-headers")
            .header("Accept-Encoding", "gzip,deflate") 
            .header("Keep-Alive", "300") 
            .header("Accept-Language", "zh-CN,zh;q=0.9") 
            .exchange()
            .expectStatus().isOk
            .expectBody()
            .jsonPath("$.encoding").isEqualTo("gzip,deflate")
            .jsonPath("$.keepAlive").isEqualTo(300)
            .jsonPath("$.language").isEqualTo("zh-CN,zh;q=0.9")
    }

    @Test
    fun `should handle missing optional header`() {
        webTestClient.get()
            .uri("/optional-headers")
            // 不提供 Authorization 头部
            .exchange()
            .expectStatus().isOk
            .expectBody()
            .jsonPath("$.isAuthenticated").isEqualTo(false)
            .jsonPath("$.apiVersion").isEqualTo("v1") // 使用默认值
    }

    @Test
    fun `should return error for missing required header`() {
        webTestClient.get()
            .uri("/basic-header")
            // 不提供必需的 User-Agent 头部
            .exchange()
            .expectStatus().isBadRequest
    }
}

总结 📝

@RequestHeader 注解是 Spring WebFlux 中处理 HTTP 请求头的强大工具,它提供了:

核心价值

  • 简化代码:无需手动解析请求头
  • 类型安全:自动类型转换和验证
  • 灵活配置:支持可选头部和默认值
  • 集成友好:与 Spring 生态系统完美集成

最佳实践总结 ✅

  1. 按需绑定:只绑定实际需要的头部,避免性能损失
  2. 合理默认:为可选头部提供合理的默认值
  3. 异常处理:实现全局异常处理器处理头部相关异常
  4. 类型转换:充分利用 Spring 的自动类型转换能力
  5. 测试覆盖:编写完整的测试用例验证头部处理逻辑

通过掌握 @RequestHeader 的使用,你可以构建更加健壮和用户友好的 WebFlux 应用程序! 🎉