Retrofit headers interceptor does not change header - android

I have android app with few api calls. I noticed that every call has
#Headers("Content-Type: application/json") annotation in ApiService so I decided remove annotation and add header via interceptor to all requests:
val headers = { chain: Interceptor.Chain ->
val request = chain.request().newBuilder()
.addHeader("Content-Type", "application/json")
.build()
chain.proceed(request)
}
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder()
.addInterceptor(headers)
.addInterceptor(logging)
.build()
val customGson = GsonBuilder()
.registerTypeAdapter(NameValuesList::class.java, NamesValuesListConverter())
.create()
val retrofit = Retrofit.Builder()
.baseUrl("http://www.$fullDomain")
.addConverterFactory(GsonConverterFactory.create(customGson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(client)
.build()
service = retrofit.create(ApiService::class.java)
But after that server return error on api call.
In logs I see that when I have explicit #Headers() annotation:
D/OkHttp: Content-Type: application/json
And after replace it with interceptor:
D/OkHttp: Content-Type: application/json; charset=UTF-8
I tried to change interceptor to this one:
val headers = { chain: Interceptor.Chain ->
val request = chain.request().newBuilder()
.headers(Headers.of(mutableMapOf("Content-Type" to "test")))
.build()
chain.proceed(request)
}
But I still see this in log:
D/OkHttp: Content-Type: application/json; charset=UTF-8
So looks like my interceptor does not apply or overridden. How to fix it?
UPD. I found the reason: when I add GsonConverterFactory it automatically add header Content-Type: application/json; charset=UTF-8. Are there any way to avoid it without implementing custom ConverterFactory?

as documentation says :
"Note: Headers do not overwrite each other. All headers with the same name will be included in the request."
refer to retrofit Headers

You can check if header exist.
val headers = { chain: Interceptor.Chain ->
val request = chain.request().newBuilder()
if(chain.request().header("Content-Type") == null){
request.addHeader("Content-Type", "application/json")
}
chain.proceed(request.build())
}

Try this
chain.request().newBuilder().removeHeader("Content-Type") .headers(Headers.of(mutableMapOf("Content-Type" to "test"))) .build()

Related

Android HTTP Headers with API

What are headers used for if I want to do a post request & response ?
How is it possible to "save" token there ?
I can't find any good explanation about it.
you can use Retrofit to call api and store token into shared preferences and add common headers to OkhttpClient
val prefs = Prefs.getInstance();
val httpClient = OkHttpClient.Builder()
httpClient.addInterceptor { chain ->
val original = chain.request()
val request = original.newBuilder()
.header("Authorization", prefs.token)
.header("Accept", "application/json")
.method(original.method, original.body)
.build()
chain.proceed(request)
}
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
httpClient.addInterceptor(interceptor)
val client = httpClient.build()
and make Retrofit object like this
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(
GsonConverterFactory.create(
GsonBuilder().setPrettyPrinting().create()
)
)
.client(client).build()

Retrofit2 send Bearer Token

I've tried sending the token with a HeaderMap but get a 401 code response. The way my project is setup is that I have a separate file for my ApiClient and I have a OkHttpClient Interceptor and a HttpLoggingInterceptor to see whats going on, however I can't get the Bearer Token to work. I've seen solutions that add it to the interceptor as a header in the interceptor and I've tried this but since my token is saved in SharedPreferences I can't get it to work in the ApiClient class I have.
This is the ApiClient
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
Gson gson = new GsonBuilder().serializeNulls().setLenient().create();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
#NotNull
#Override
public okhttp3.Response intercept(#NotNull Chain chain) throws IOException {
Request originalRequest = chain.request();
Request newRequest = originalRequest.newBuilder()
//I would add the header here
//I tried this but it says on "ApiClient.this" cannot be referenced from static context
// .header("Authorization" , SharedPreferencesHelper.getUserToken(ApiClient.this));
.build();
return chain.proceed(newRequest);
}
})
.addInterceptor(interceptor)
.build();
retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.0.6:8000/api/")
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
This is the method from SharedPreferencesHelper.getUserToken(MainActivity.this)
public static String getUserToken(Context context) {
SharedPreferences sp = getSharedPreferences(context);
return sp.getString(USER_TOKEN, null);
}
This is the current call where the response is 401, If I don't add the Accept => application/json the response url is incorrect and also returns a html page when I need a simple response return response("LoggedOut", 200); //this is the response in the api
Map<String, String> headers = new HashMap<>();
headers.put("Accept", "application/json");
headers.put("Token", SharedPreferencesHelper.getUserToken(MainActivity.this));
Call<Void> call = apiInterface.LogoutUser(headers);
call.enqueue(new Callback<Void>() {
// onResponse and onFailure here
}
For example without the Accept header this is the response in the Logcat
D/OkHttp: --> GET http://192.168.0.6:8000/api/logout
D/OkHttp: Token: wE1Y8IxJpwyXtvw0fYoXZAlQ6qCx24YtzonQIeJBQSHmNppe0Sn1kLYDgZKCw4MKbpab4Vspf61Nzer1
D/OkHttp: --> END GET
D/OkHttp: <-- 200 OK http://192.168.0.6:8000/login
//a bunch of html that's the web page at this route, notice the /api is missing
How can I send this correctly?
EDIT:
I"m using a Laravel project for the backend and this is the relevant route
Route::middleware('auth:sanctum')
->get('/logoutApi', function (Request $request) {
$request->user()->tokens()->delete();
return response("LoggedOut", 202);
});
create class Authenticator, like:
const val HEADER_TOKEN_FIELD = "Authorization"
class ClassAuthenticator(
private val pref: SharedPref
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
return response.request().newBuilder()
.header(HEADER_TOKEN_FIELD, pref.getToken())
.build()
}
}
then add interceptor in your client with:
val httpClient = OkHttpClient.Builder()
.authenticator(ClassAuthenticator(pref))
.addInterceptor { chain ->
val request = chain.request()
val httpRequest = request.newBuilder()
.addHeader(HEADER_TOKEN_FIELD,
"Bearer ${pref.getToken()}")
.build()
val response = chain.proceed(httpRequest)
response
}
.build()

500 error using Skyscanner API with Kotlin and Retrofit

I want to connect to the Skyscanner API, using Kotlin and Retrofit. https://rapidapi.com/skyscanner/api/skyscanner-flight-search
When attempting to POST the 'create session' call, I get a 500 error, but the logs aren't giving a specific reason. I can only assume that my post data isn't being formatted correctly, but I'm using Retrofit with GSon to handle this for me.
One clue, is that in their Java sample code, they pass the form data in the following format: "inboundDate=2019-09-10&children=0&adults=1" whereas after GSon convertion from my sessionObject class, my data is in the format {"adults":1,"country":"GB","outboundDate":"2020-01-06"} - I'm unsure how, using Retrofit, I can pass my data in that format, and whether that's the issue causing the 500.
Here are some code snippets:
// my object for posting data
class SessionBody {
#SerializedName("country")
var country: String = ""
#SerializedName("currency")
var currency: String = ""
...etc...
// my interface
#Headers("Content-Type: application/x-www-form-urlencoded")
#POST("pricing/v1.0/")
fun postUser(#Body sessionBody: SessionBody): Call<Void>
// my connector class
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder()
.addInterceptor(Interceptor { chain ->
val original = chain.request()
val request = original.newBuilder()
.header("X-RapidAPI-Host", "skyscanner-skyscanner-flight-search- v1.p.rapidapi.com")
.header("X-RapidAPI-Key", "...my key here...")
.method(original.method(), original.body())
.build()
return#Interceptor chain.proceed(request)
})
.addInterceptor(interceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
.client(client)
.build()
val api = retrofit.create(TravelEzyApi::class.java)
val call = api.postUser(sessionBody)
And here is the output from the logs...
D/OkHttp: --> POST https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com/apiservices/pricing/v1.0/
D/OkHttp: Content-Type: application/x-www-form-urlencoded
Content-Length: 142
X-RapidAPI-Host: skyscanner-skyscanner-flight-search-v1.p.rapidapi.com
X-RapidAPI-Key: ... my key here ...
{"adults":1,"country":"GB","currency":"GBP","destinationPlace":"BKK-sky","locale":"en-GB","originPlace":"LHR-sky","outboundDate":"2020-01-06"}
--> END POST (142-byte body)
D/OkHttp: <-- 500 Internal Server Error
D/OkHttp: Cache-Control: private
Content-Type: application/json
Date: Tue, 22 Oct 2019 10:48:44 GMT
Server: RapidAPI-1.0.32
X-RapidAPI-Region: AWS - eu-west-1
X-RapidAPI-Version: 1.0.32
Content-Length: 2
Connection: keep-alive
{}
D/OkHttp: <-- END HTTP (2-byte body)
Any help or clues greatly appreciated.
The 500 Status code indicates that the server has encountered a situation it does not know how to handle.
This should work fine by now. You can always write to the RapidAPI support team if the error still persists at support#rapidapi.com

Why is this OkHttp POST that supports a BODY missing its Content-Type Header?

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)
Why is it not working
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.
What to to instead
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.
Rocky alternative
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:
convertor factory
request body convertor (transforms the #Body to okhttp3.RequestBody)
repsonse body convertor (transforms the okhttp3.ResponseBody to return value)

How to specify Get-Request encoding (Retrofit + OkHttp)

I'm using Retrofit2 + OkHttp3 in my Android app to make a GET - Request to a REST-Server. The problem is that the server doesn't specify the encoding of the JSON it delivers. This results in an 'é' being received as '�' (the Unicode replacement character).
Is there a way to tell Retrofit or OkHttp which encoding the response has?
This is how I initialize Retrofit (Kotlin code):
val gson = GsonBuilder()
.setDateFormat("d.M.yyyy")
.create()
val client = OkHttpClient.Builder()
.build()
val retrofit = Retrofit.Builder()
.baseUrl(RestService.BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
val rest = retrofit.create(RestService::class.java)
PS: The server isn't mine. So I cannot fix the initial problem on the server side.
Edit: The final solution
class EncodingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
val mediaType = MediaType.parse("application/json; charset=iso-8859-1")
val modifiedBody = ResponseBody.create(mediaType, response.body().bytes())
val modifiedResponse = response.newBuilder()
.body(modifiedBody)
.build()
return modifiedResponse
}
}
One way to do this is to build an Interceptor that takes the response and sets an appropriate Content-Type like so:
class ResponseInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
val modified = response.newBuilder()
.addHeader("Content-Type", "application/json; charset=utf-8")
.build()
return modified
}
}
You would add it to your OkHttp client like so:
val client = OkHttpClient.Builder()
.addInterceptor(ResponseInterceptor())
.build()
You should make sure you either only use this OkHttpClient for your API that has no encoding specified, or have the interceptor only add the header for the appropriate endpoints to avoid overwriting valid content type headers from other endpoints.
class FixEncodingInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
MediaType oldMediaType = MediaType.parse(response.header("Content-Type"));
// update only charset in mediatype
MediaType newMediaType = MediaType.parse(oldMediaType.type()+"/"+oldMediaType.subtype()+"; charset=windows-1250");
// update body
ResponseBody newResponseBody = ResponseBody.create(newMediaType, response.body().bytes());
return response.newBuilder()
.removeHeader("Content-Type")
.addHeader("Content-Type", newMediaType.toString())
.body(newResponseBody)
.build();
}
}
and add to OkHttp:
builder.addInterceptor(new FixEncodingInterceptor());
This post is old but I found a solution that works for me in Kotlin (the answer of #BryanHerbst didn't quite worked for me)
class EncodingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
var encodedBody = ""
val encoding = InputStreamReader(
response.body?.byteStream(),
Charset.forName("ISO-8859-1")
).forEachLine {
encodedBody += it
}
return response.newBuilder()
.addHeader("Content-Type", "application/xml; charset=utf-8")
.body(encodedBody.toResponseBody())
.build()
}
}

Categories

Resources