Reputation: 1432
I saw that Content-Type
header is removed for methods that don't support a Body
, but that isn't my case. I've also confirmed my User-Agent
header is successfully set.
This can be done statically via the interface with the endpoint's definition but I'd favor a global Interceptor
over annotating all my methods.
// 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)
Upvotes: 0
Views: 3300
Reputation: 38243
Retrofit is in charge of setting appropriate content type and length based on registered converters and what you provide to your @Body
parameter.
In greater detail: A Retrofit converter is responsible for transforming the type of your @Body
to okhttp3.RequestBody
which holds your content bytes, content length, and content type. Similarly on the way back. You supply content, ResponseBody
handles details like HTTP headers.
You can't manually override these headers.
As you can see in the log, your string body gets successfully transmitted as 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
That leads me to believe you have a registered converter and it's the scalar converter, which states:
A Converter which supports converting strings and both primitives and their boxed types to
text/plain
bodies.
All of the ready-made converters (Moshi, Gson, Jackson) are built to convert POJOs to application/json
. This is a typical case and you should use one of these if you can. Explore source code here.
There are plenty of tutorials online for this case.
If for some reason you want/need to continue your current direction, that is prepare a JSON string manually and send that as application/vnd.api+json
, you'll need a custom converter.
The aforementioned scalar converter already knows how to transform strings, so copy it into your project and adapt it to your needs (change the mime type). It's just a set of three classes:
@Body
to okhttp3.RequestBody
)okhttp3.ResponseBody
to return value)Upvotes: 1