You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OkHttp Interceptor无法读取请求的Content-Type请求头问题排查

问题

我需要创建一个OkHttp interceptor,用于读取带请求体的POST请求的Content-Type请求头的值。但当我尝试通过request.headers访问该请求头时,却找不到它(能看到其他自定义请求头,但看不到Content-Type),不过在HttpLoggingInterceptor的日志中却能清晰看到它。

我使用的是Retrofit。

示例代码

//versions:
//okHttp: 4.10.0
//retrofit: 2.9.0

interface API {
    @Headers("secret: aaa", "aaa: AAA", "bbb: BBB", "Content-Type: application/json")
    @POST("/post")
    suspend fun echoPOST(@Body bodyData: BodyData): Response<String>
}

data class BodyData(val secret: String = "aaaa", val nonsecret: String = "bbb")

val httpClient = OkHttpClient.Builder()
            .addInterceptor(
                HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
            )
            .addInterceptor(MyInterceptor())
            .build()
val testRetrofitClient = Retrofit.Builder()
            .baseUrl("https://httpbin.org")
            .addConverterFactory(ScalarsConverterFactory.create())
            .addConverterFactory(GsonConverterFactory.create())
            .client(httpClient)
            .build()

val api = testRetrofitClient.create(API::class.java)

suspend fun testRequestPOST() {
        try {
            val response = api.echoPOST(BodyData())
        } catch (e: Exception) {
            Timber.e(e)
        }
}

拦截器代码

class MyInterceptor : Interceptor {
    override fun intercept(chain: Chain): Response {
        val request = chain.request()
        val response = chain.proceed(request)
        Log.d("HTTP", "Content-Type: ${request.header("Content-Type")}")
        Log.d("HTTP", "request headers: ${request.headers.joinToString { it.first }}")
        return response
    }
}

日志输出

// output from HttpLoggingInterceptor
--> POST https://httpbin.org/post
Content-Type: application/json; charset=UTF-8
Content-Length: 35
secret: aaa
bbb: bbb123
ccc: ccc123
{"nonsecret":"bbb","secret":"aaaa"}

// output from MyInterceptor
Content-Type: null
request headers: secret, bbb, ccc

可见Content-Type请求头确实存在(HttpLoggingInterceptor已记录),但在MyInterceptor中不可见。请问我哪里出错了?


解决方案

核心原因

  1. 拦截器执行时机问题:你通过addInterceptor添加的是应用拦截器,这类拦截器在Retrofit处理请求(包括设置Content-Type头)之前执行,此时Content-Type还未被添加到请求头中。而HttpLoggingInterceptor能看到完整头,是因为它会在请求最终发送前的阶段捕获请求。
  2. 手动设置的Content-Type被覆盖:即使你在@Headers中指定了Content-Type,当使用@Body注解时,Retrofit的转换器(如GsonConverterFactory)会自动生成并设置标准的Content-Type头,覆盖你手动添加的内容,且这个操作发生在应用拦截器执行之后。

两种解决办法

办法1:改用网络拦截器

将自定义拦截器通过addNetworkInterceptor添加,这类拦截器在请求即将发送到服务器前执行,此时所有请求头都已被Retrofit处理完毕:

val httpClient = OkHttpClient.Builder()
            .addInterceptor(
                HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
            )
            .addNetworkInterceptor(MyInterceptor()) // 替换为网络拦截器
            .build()

办法2:直接从请求体获取Content-Type

如果必须使用应用拦截器,可以跳过请求头,直接从请求体的contentType()方法读取,这是Content-Type头的实际来源:

class MyInterceptor : Interceptor {
    override fun intercept(chain: Chain): Response {
        val request = chain.request()
        val response = chain.proceed(request)
        // 直接从请求体获取媒体类型
        val contentType = request.body?.contentType()?.toString()
        Log.d("HTTP", "Content-Type: $contentType")
        Log.d("HTTP", "request headers: ${request.headers.joinToString { it.first }}")
        return response
    }
}

额外说明:使用@Body发送JSON时,无需手动在@Headers中添加Content-Type,Retrofit的转换器会自动设置正确的application/json; charset=UTF-8头。


内容的提问来源于stack exchange,提问作者Piotr Śmietana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 16:16:01