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中不可见。请问我哪里出错了?
解决方案
核心原因
- 拦截器执行时机问题:你通过
addInterceptor添加的是应用拦截器,这类拦截器在Retrofit处理请求(包括设置Content-Type头)之前执行,此时Content-Type还未被添加到请求头中。而HttpLoggingInterceptor能看到完整头,是因为它会在请求最终发送前的阶段捕获请求。 - 手动设置的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
相关产品推荐
相关产品推荐

