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

Retrofit2+OkHttp发送POST请求时Content-Type请求头丢失问题

问题现象

发送携带非空请求体、同时包含Cookie、Content-Type请求头的POST请求时,通过ChuckerInterceptor查看请求日志发现Content-Type头丢失;已排除请求体为空触发OkHttp自动移除该头的场景,先后尝试接口注解、自定义拦截器两种方式添加Content-Type均未生效。

根因定位
  • OkHttpClient构建逻辑错误:现有代码在添加ChuckerInterceptor后立刻调用.build()生成了不可变的OkHttpClient实例,后续的CookieInterceptor(即使取消注释)也无法被添加到拦截器链中——addInterceptor是OkHttpClient.Builder的方法,无法在已构建完成的OkHttpClient实例上调用生效,这也是自定义拦截器添加Content-Type失效的核心原因。
  • Content-Type重复设置冲突:使用@Body注解搭配GsonConverterFactory时,转换器会自动为生成的RequestBody绑定application/json; charset=utf-8的Content-Type,手动在拦截器/接口注解中重复添加该头,会被OkHttp以请求体自身携带的ContentType为准覆盖,部分场景下会出现头被移除的表现。
  • 低版本ChuckerInterceptor存在已知的POST请求头展示不全bug,可能出现头实际存在但日志面板不展示的情况。
修复方案

1. 修正OkHttpClient构建逻辑

调整链式调用顺序,所有拦截器、客户端配置全部添加完成后再调用build()方法,拦截器按执行顺序注册:

@Singleton
@Provides
@Named("rkkFilter")
fun rkkFilter(@ApplicationContext context: Context): rkkFilter =
    Retrofit.Builder()
        .baseUrl(BASE_URL)
        .client(
            OkHttpClient.Builder()
                // 先注册业务拦截器,再注册抓包拦截器
                .addInterceptor(CookieInterceptor(context))
                .addInterceptor(ChuckerInterceptor(context))
                // 所有配置完成后再构建客户端
                .build()
         )
        .addConverterFactory(
            GsonConverterFactory.create(
                GsonBuilder()
                    .setLenient()
                    .create()
            )
        )
        .build()
        .create(rkkFilter::class.java)

2. 简化CookieInterceptor逻辑

移除手动添加Content-Type的代码,该头由GsonConverterFactory自动绑定到请求体即可,避免重复设置冲突,同时补全SharedPreferences取值的空安全处理:

class CookieInterceptor(context: Context) : Interceptor {
    private val pref: SharedPreferences = context.getSharedPreferences(
        "prefs",
        Context.MODE_PRIVATE
    )
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request().newBuilder()
            .addHeader("Cookie", pref.getString("id", "NOVALUE") ?: "NOVALUE")
            .build()
        return chain.proceed(request)
    }
}

3. 接口定义无需调整

保持原有写法即可,GsonConverterFactory会自动处理Body的序列化和Content-Type设置:

interface rkkFilter {
    @POST("rkk/filter")
    suspend fun filterRkk(
        @Body rkkFilter: RkkFilter
    ): Response<ResponseBody>
}
验证方式

如果修正后Chucker面板仍未展示Content-Type,可添加临时日志拦截器打印真实请求信息,确认头是否实际存在:

class HeaderCheckInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        // 打印全部请求头
        request.headers.forEach { (name, value) ->
            Log.d("ReqHeader", "$name: $value")
        }
        // 打印请求体绑定的Content-Type
        Log.d("ReqBody", "contentType = ${request.body?.contentType()}")
        return chain.proceed(request)
    }
}

将该拦截器注册到ChuckerInterceptor之前,如果日志中能正常打印Content-Type值,说明头实际已携带,升级Chucker到最新版本即可解决展示异常问题。

内容的提问来源于stack exchange,提问作者Maxim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 02:57:18