为什么这个支持BODY的OkHttp POST缺少其内容类型标头?



我看到Content-Type不支持Body的方法删除了标头,但事实并非如此。我还确认我的User-Agent标头已成功设置。

这可以通过具有端点定义的接口静态完成,但我更喜欢全局Interceptor而不是注释我的所有方法。

// Api.kt
@POST("authenticated_users")
fun postUser(
@Body newUser: NewUser
): Observable<AuthUser>
class UserRepo @Inject constructor(private val api: Api) {
fun postUser(newUser: NewUser) = api.postUser(newUser)
}
// NetModule.kt
@Provides @Singleton
fun providesOkHttpClient(cache: Cache, app: Application): OkHttpClient {
val timeoutInSeconds = 90.toLong()
val builder = OkHttpClient.Builder()
.cache(cache)
.addInterceptor(MyInterceptor(app))
.connectTimeout(timeoutInSeconds, TimeUnit.SECONDS)
.readTimeout(timeoutInSeconds, TimeUnit.SECONDS)
when {
BuildConfig.DEBUG -> {
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.HEADERS
}
with(builder) {
addInterceptor(loggingInterceptor)
addNetworkInterceptor(StethoInterceptor())
}
}
}
return builder.build()
}
@Provides @Singleton
fun providesMoshi(): Moshi {
val jsonApiAdapterFactory = ResourceAdapterFactory.builder()
.add(TermsConditions::class.java)
.add(AuthUser::class.java)
.add(Unknown::class.java)
.build()
val builder = Moshi.Builder()
.add(jsonApiAdapterFactory)
.add(KotlinJsonAdapterFactory())
return builder.build()
}
@Provides @Singleton
fun providesRetrofit(okHttpClient: OkHttpClient, moshi: Moshi): Retrofit {
return Retrofit.Builder()
// .addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(JsonApiConverterFactory.create(moshi))
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(baseUrl)
.client(okHttpClient)
.build()
}
// MyInterceptor.kt
class MyInterceptor @Inject constructor(private val app: Application) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val initialRequest = chain.request()
val finalRequest = setHeaders(initialRequest)
return chain.proceed(finalRequest)
}
private fun setHeaders(initialRequest: Request): Request {
return initialRequest.newBuilder()
// .header("Content-Type", "application/vnd.api+json")
.header("User-Agent", "MyApp v${BuildConfig.VERSION_NAME}")
.build()
}
}
// MyViewModel.kt
fun createUser() {
userObserver = object : DisposableObserver<AuthUser>() {
override fun onNext(authUser: AuthUser) {
statusData.postValue(true)
}
override fun onError(e: Throwable) {
Timber.w(e.localizedMessage)
error.postValue(e.localizedMessage)
}
override fun onComplete() {
// no-op
}
}
userRepo.postUser(newUser)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(userObserver)
}
// log1.txt Retrofit with ScalarsConverterFactory
2018-04-18 15:20:35.772 16491-17436/com.es0329.myapp D/OkHttp: --> POST https://api.es0329.com/v5/authenticated_users
Content-Type: text/plain; charset=UTF-8
Content-Length: 259
User-Agent: MyApp v1.5.1
--> END POST
2018-04-18 15:20:36.278 16491-17436/com.es0329.myapp D/OkHttp: <-- 500 https://api.es0329.com/v5/authenticated_users (505ms)
// log2.txt Retrofit without ScalarsConverterFactory
2018-04-18 18:25:45.742 5017-6325/com.es0329.myapp D/OkHttp: --> POST https://api.es0329.com/v5/authenticated_users
Content-Type: application/json; charset=UTF-8
Content-Length: 311
User-Agent: MyApp v1.5.1
--> END POST
2018-04-18 18:25:45.868 5017-6325/com.es0329.myapp D/OkHttp: <-- 500 https://api.es0329.com/v5/authenticated_users (125ms)
// log3.txt after modifying JsonApiConverterFactory's `MediaType`
2018-04-18 20:35:47.322 19368-19931/com.es0329.myapp D/OkHttp: --> POST https://api.es0329.com/v5/authenticated_users
Content-Type: application/vnd.api+json
Content-Length: 268
User-Agent: MyApp v1.5.1
--> END POST
2018-04-18 20:35:49.058 19368-19931/com.es0329.myapp D/OkHttp: <-- 200 https://api.es0329.com/v5/authenticated_users (1735ms)

为什么它不起作用

Retrofit 负责根据注册的转换器以及您提供给@Body参数的内容设置适当的内容类型和长度。

更详细地说:改造转换器负责将@Body的类型转换为保存内容字节、内容长度和内容类型的okhttp3.RequestBody。同样在回来的路上。您提供内容,ResponseBody处理 HTTP 标头等详细信息。

您无法手动覆盖这些标头。

正如您在日志中看到的,您的字符串主体已成功传输为text/plain.

--> POST https://api.es0329.com/v5/authenticated_users
Content-Type: text/plain; charset=UTF-8
Content-Length: 259
User-Agent: MyApp v1.5.1
--> END POST

这让我相信你有一个注册的转换器,它是标量转换器,它指出:

支持将字符串和基元及其盒装类型转换为text/plain体的转换器。

代替什么

所有现成的转换器(Moshi,Gson,Jackson)都是为了将POJO转换为application/json而构建的。这是一个典型的情况,如果可以的话,你应该使用其中之一。在此处探索源代码。

网上有很多关于这种情况的教程。

洛基替代品

如果出于某种原因您希望/需要继续当前方向,即手动准备一个 JSON 字符串并将其作为application/vnd.api+json发送,您将需要一个自定义转换器。

前面提到的标量转换器已经知道如何转换字符串,因此请将其复制到您的项目中并根据您的需求进行调整(更改 mime 类型)。它只是一组三个类:

  • 转换器工厂
  • 请求正文转换器(将@Body转换为okhttp3.RequestBody)
  • repsonse 主体转换器(将okhttp3.ResponseBody转换为返回值)

最新更新