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
相关产品推荐
相关产品推荐

