代码之家  ›  专栏  ›  技术社区  ›  es0329

为什么这篇OkHttp文章支持的正文缺少其内容类型标头?

  •  0
  • es0329  · 技术社区  · 6 年前

    我看到了 Content-Type header is removed for methods that don't support a 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)
    
    1 回复  |  直到 6 年前
        1
  •  1
  •   Eugen Pechanec    6 年前

    为什么不起作用

    Reformation负责根据注册的转换器和您提供给您的 @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
    

    这让我相信你有一个注册的转换器 scalar converter ,其中说明:

    一种转换器,支持将字符串和基本体及其装箱类型转换为 文本/普通 尸体。

    改为什么

    所有现成的转换器(Moshi、Gson、Jackson)都是为了将POJO转换为 application/json 。这是一个典型的案例,如果可以,您应该使用其中一个。 Explore source code here.

    网上有很多关于这个案例的教程。

    洛基备选方案

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

    上述 标量转换器 已经知道如何转换字符串,所以请将其复制到项目中并根据需要进行调整(更改mime类型)。这只是一组三个类:

    • 转炉厂
    • 请求正文转换器(转换 @车身 okhttp3。请求主体 )
    • repsonse车身转换器(转换 okhttp3.ResponseBody 返回值)