I'm using Retrofit with OkHttp Interceptor to work with API.
Interceptor adding cookie header to each request.
Interceptors code:
class AddCookiesInterceptor: Interceptor {
#Inject
lateinit var cookiesDao: CookiesDao
init {
App.getAppComponent().inject(this)
}
#SuppressLint("CheckResult")
override fun intercept(chain: Interceptor.Chain): Response {
val builder = chain.request().newBuilder()
cookiesDao.getAll()
.subscribeOn(Schedulers.io())
.subscribe { cookies ->
builder.addHeader("Cookie", "JWT=" + cookies.jwt)
}
return chain.proceed(builder.build())
}
}
While debuging i see, that interceptor updates request and adds cookie header with value, but when server reachs the request it returns an error (400 http code auth again).
if i manualy add Header into request like this
#GET("/api.tree/get_element/")
#Headers("Content-type: application/json", "X-requested-with: XMLHttpRequest", "Cookie: jwt_value")
fun getElementId(): Maybe<ResponseBody>
Api returns 200 http code and it works.
Your code is not working because you are adding the header asynchronously, this is a "timeline" of what's happening in your flow:
init builder -> ask for cookies -> proceed with chain -> receive cookies dao callback -> add header to builder which has been already used
What you need to do is retrieve the cookies synchronously, to accomplish this you can use the BlockingObseervable and get something like this.
Using a synchronous function won't cause any trouble since the interceptor is already running on a background thread.
#SuppressLint("CheckResult")
override fun intercept(chain: Interceptor.Chain): Response {
val builder = chain.request().newBuilder()
val cookies = cookiesDao.getAll().toBlocking().first()
builder.addHeader("Cookie", "JWT=" + cookies.jwt)
return chain.proceed(builder.build())
}
Related
What is the better approach to regenerate the JWT token using refresh token in Apollo Graphql in Andriod Kotlin?.
Now we are using an Auth interceptor that extends ApolloInterceptor to attach the JWT token in the request header.
I want to check whether the Toke expires from here and it should call another mutation to generate a new token. Then it should proceed with the previous call with the new token.
Please refer to the code below
class AuthInterceptor(private val jwtToken: String) : ApolloInterceptor {
override fun interceptAsync(
request: ApolloInterceptor.InterceptorRequest,
chain: ApolloInterceptorChain,
dispatcher: Executor,
callBack: ApolloInterceptor.CallBack
) {
val header = request.requestHeaders.toBuilder().addHeader(
HEADER_AUTHORIZATION,
"$HEADER_AUTHORIZATION_BEARER $jwtToken"
).build()
chain.proceedAsync(request.toBuilder().requestHeaders(header).build(), dispatcher, callBack)
}
override fun dispose() {}
companion object {
private const val HEADER_AUTHORIZATION = "Authorization"
private const val HEADER_AUTHORIZATION_BEARER = "Bearer"
}
}
If you are using Apollo Android Client V3 and Using Kotlin Coroutine then use Apollo Runtime Dependency and Try HttpInterceptor instead of ApolloInterceptor. I think this is the better/best approach. For Reference Click Here
In your app-level build.gradle file
plugins {
......
id("com.apollographql.apollo3").version("3.5.0")
.....
}
dependencies {
implementation("com.apollographql.apollo3:apollo-runtime")
}
Now write your interceptor for the Apollo client.
FYI: If you've added the Authorization header using Interceptor or using addHttpHeader in client already then remove it or don't add header here val response = chain.proceed(request.newBuilder().addHeader("Authorization", "Bearer $token").build()), just build the request. Otherwise Authorization header will add multiple times in the request. So, be careful.
class AuthorizationInterceptor #Inject constructor(
val tokenRepo: YourTokenRepo
) : HttpInterceptor {
private val mutex = Mutex()
override suspend fun intercept(request: HttpRequest, chain: HttpInterceptorChain): HttpResponse {
var token = mutex.withLock {
// get current token
}
val response = chain.proceed(request.newBuilder().addHeader("Authorization", "Bearer $token").build())
return if (response.statusCode == 401) {
token = mutex.withLock {
// get new token from your refresh token API
}
chain.proceed(request.newBuilder().addHeader("Authorization", "Bearer $token").build())
} else {
response
}
}
}
Configure your Apollo client like below.
ApolloClient.Builder()
.httpServerUrl(BASE_GRAPHQL_URL)
.webSocketServerUrl(BASE_GRAPHQL_WEBSOCKET_ENDPOINT) // if needed
.addHttpHeader("Accept", "application/json")
.addHttpHeader("Content-Type", "application/json")
.addHttpHeader("User-Agent", userAgent)
.addHttpInterceptor(AuthorizationInterceptor(YourTokenRepo))
.httpExposeErrorBody(true)
.build()
In iOS development, when I fetch an URL that displays XML, I can parse the whole XML file and use its data in my code, but in Kotlin I tried fetching the same URL and it returns only the first XML tag, like if the rest was hidden in the main tag.
val urlString = URL_TO_FETCH_IN_HTTPS (String)
val client = OkHttpClient()
val request = Request.Builder().url(urlString).build()
client.newCall(request).enqueue(object: Callback {
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
val body = response.body?.string()
println("RESPONSE " + body)
}
}
override fun onFailure(call: Call, e: IOException) {
println("Failure")
}
})
The response of this call is just
RESPONSE < ?xml version="1.0" encoding="UTF-8"?>< rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
Although the url is good, and the returned XML in a browser is also good.
So what do I do wrong in the code? How can I fetch the whole XML at this url?
I used the library OkHttp for fetching data from a URL
First of all to debug Okhttp i would suggest to add an interceptor :
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.HEADERS
logging.level = HttpLoggingInterceptor.Level.BODY
Then build you client like this to first add interceptor and have handle timeout :
client = OkHttpClient.Builder()
.addInterceptor(logging)
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build()
Then you can : client.newCall(request).enqueue(object : Callback { ...
By doing so you can easily debug Okhttp you will have on the log the request that you made with the parameter send + then response of the server with the code and the body. All call are writen in blue for better visibilitie.
if you still got probleme don't hesitate to ask.
More over you should look at retrofit2 it's an easy lib to handle all apicall.
I have an Authenticator like this
#Singleton
class TokenAutheticator #Inject constructor(private val tokenHolder: Lazy<TokenHolder>,private val tokenInterceptor: TokenInterceptor):Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
val resp = tokenHolder.get().tokenService.relogin(tokenInterceptor.token).execute()
println("### res "+resp.code())
if (resp.code()==200) {
val body = tokenHolder.get().tokenService.relogin(tokenInterceptor.token).execute()?.body()
val newToken = body?.token
println("########## authenticator ########## ")
val url = route?.address()?.url()?.newBuilder()?.addQueryParameter("token", newToken)?.build()
return response.request().newBuilder().url(url).build()
}else{
return null
}
}
}
When the resp.code != 200 the Authenticator is called multiple times.
I am plugging it in Okhttp like this
#Provides
#Singleton
fun provideOkhttp(tokenInterceptor: TokenInterceptor, tokenAutheticator: TokenAutheticator): OkHttpClient {
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder()
.authenticator(tokenAutheticator)
.addInterceptor(logging)
.addInterceptor(tokenInterceptor)
.build()
return client
}
So what I want to do is have the Authenticator try it only once and if it is able to get a new token then use the new token from now on and if it can't get a new token then exit. But the Authenticator is called multiple times and the API responds with Too many attempts. Any help is appreciated. Thanks
Only when you return null, Authenticator will stop getting called (stop retrying authentication).
Since you always return something, when response code is 200, it will get called a few times. Luckily, as far I understand, Authenticator detects your endless loop, and breaks out of it eventually.
Basically, you should use Authenticator to react to 401 (and similar) response code and only add authorization header in that case.
I am trying to post data to REST API server with retrofit + RxJava . When I am trying to send data to server , it said " HTTP 500 Internal Server Error Occurred". But when the data is send with POSTMAN, it succeeded.
This is the function for sending data in Model.
// Encountering with 500 server error
fun postSchedule(data : ScheduleResponse , errorLD: MutableLiveData<String>){
Log.d("POST DATA", "${data.title} ${data.remindMeAt}" )
userClient.postScheduleItem(data)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.subscribe(object : io.reactivex.Observer<ServerResponse>{
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: ServerResponse) {
errorLD.value = t.status
}
override fun onError(e: Throwable) {
errorLD.value = e.message
}
})
}
This is my API interface
#Headers("Accept: application/json")
#POST("schedules")
fun postScheduleItem(#Body data: ScheduleResponse): Observable<ServerResponse>
This is the retrofit client.
val httpLoggingInterceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
val httpClient = OkHttpClient.Builder()
var dbInstance: TodoDB = TodoDB.getInstance(context)
var rxJavaAdapter = RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())
val retrofitBuilder =
Retrofit.Builder()
.baseUrl(AppConstants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(rxJavaAdapter)
fun <T> createService(serviceClass: Class<T>, authToken: String?): T {
if (!TextUtils.isEmpty(authToken)) {
val interceptor = AuthenticationInterceptor(authToken!!)
if (!httpClient.interceptors().contains(interceptor)) {
httpClient.addInterceptor(interceptor)
retrofitBuilder.client(httpClient.build())
}
}
return retrofitBuilder.build().create(serviceClass)
}
Please help me with this.Thank you.
Client side code is not enough to determine what causes the server to respond with 500. The best you can do is start debugging the issue.
There are several directions you can go:
If you have access to the server or know someone who does, you could debug the server and determine what causes the Internal server error. Maybe the server logs can help as well and you don't have to actually step through the server code.
If you don't have access to the server, you could look at the body of the server response. Maybe there's a detailed error description there in html, json or some other format that will help you find out the root cause.
If the above steps don't help then it's very useful that you know the request works with POSTMAN. You can compare the exact POSTMAN request with the exact Retrofit request, header by header,
line by line. To do that, you should first add your httpLoggingInterceptor to your okhttp client builder with
val httpClient = OkHttpClient.Builder().addNetworkInterceptor(httpLoggingInterceptor)
and look for the request log in logcat.
If you spot the differences between the working and the not working requests, then you should work your way through all the differences, and adjust the retrofit request by adding or modifying headers using okhttp interceptors so that, at the end, the retrofit request looks exactly the same as the POSTMAN request. I suggest you remove the AuthenticationInterceptor at first and simulate it "manually" with a custom interceptor and a hard coded auth token.
Retry the request every time you eliminate a difference to isolate the cause of the internal server error.
Hope this helps!
I need to add some authorization information from cookie in response to next requests.
It works in postman - I make authorization request, then second request, which works fine. But if I delete cookies - second request returns error and I have to do authorization request again.
But in my application this second request always returns the same error. I tried to find needed cookie by using interceptor, but I hadn't found it
val client = OkHttpClient.Builder()
.addInterceptor(OAuthInterceptor())
private class OAuthInterceptor : Interceptor {
override fun intercept(chain: Chain): Response {
val request = chain.request()
com.app.logic.toLog("${chain.proceed(request).header("set-cookie")} ") // it's not that cookie what I looking for
val headers = chain.proceed(request).headers()
headers.names().forEach {
val s = headers.get(it)
com.app.logic.toLog("$it -> $s")
}
return chain + (Session.authConsumer?.let { consumer ->
consumer.sign(request).unwrap() as Request
} ?: request)
}
}
Does anybody know what else could I try?
So, finally I've found solution for working with cookies
val client = OkHttpClient.Builder()
.cookieJar(UvCookieJar())
private class UvCookieJar : CookieJar {
private val cookies = mutableListOf<Cookie>()
override fun saveFromResponse(url: HttpUrl, cookieList: List<Cookie>) {
cookies.clear()
cookies.addAll(cookieList)
}
override fun loadForRequest(url: HttpUrl): List<Cookie> =
cookies
}
You can use this this gist on how to intercept cookies when you receive them and send them back in your request in header.