I have an interceptor that handles some authentication things. If the authentication fails it throws and Exception. According to what I could find throwing an exception should result in onFailure getting called where I handle the exception. Unfortunatly this does not happen and the app simply crashes completely. I am sure I must be missing something but I cant seem to figure it out. Hope someone here can help :)
Code and stack trace below:
val client = OkHttpClient.Builder()
// Add interceptor that throws
.addInterceptor { chain ->
throw Exception("test")
}
.build()
retrofit = Retrofit.Builder()
.baseUrl(baseURL)
.client(client)
.build()
api = retrofit.create(ApiInterface::class.java)
// Create api call...
apicall.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>?, response: retrofit2.Response<T>?) {
// ....
}
override fun onFailure(call: Call<T>?, t: Throwable?) {
// Expect to go here to handle the Exception. But the app crashes
}
})
Stack trace:
E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
Process: com.testapp.test.debug, PID: 28891
java.lang.Error: java.lang.Exception: test
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1168)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
Caused by: java.lang.Exception: test
at com.testapp.test.data.networking.RetrofitWebApi$client$1.intercept(RetrofitWebApi.kt:90)
at com.testapp.test.data.networking.RetrofitWebApi$client$1.intercept(RetrofitWebApi.kt:76)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:185)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:135)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
OkHttp will only catch the exception that happens in the Interceptor if it's an IOException. You can see the code that does this here, the relevant parts (simplified) are as below:
try {
Response response = getResponseWithInterceptorChain();
} catch (IOException e) {
responseCallback.onFailure(RealCall.this, e);
}
So if you change your code to the following, you'll get a callback in your onFailure method as expected:
val client = OkHttpClient.Builder()
// Add interceptor that throws
.addInterceptor { chain ->
throw IOException("test")
}
.build()
You should never throw exception which is not IOException in OkHttp's Interceptor since it will not handled by OkHttp.
Check below source code from OkHttp's RealCall.java:
Related
I'm trying to create an Interceptor in the event that the API I'm using goes down, which has happened when I tried to make an API call on Postman only for it to return a 504 error.
This is the OkHttpClient I have for now. I set it to 5 seconds only for testing purposes.
val client = OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.addInterceptor(object : Interceptor {
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
val response = chain.proceed(chain.request())
when (response.code()) {
504 -> {
//Show Bad Request Error Message
}
}
return response
}
})
.build()
searchRetrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(URL)
.client(client)
.build()
Later in the code, I use Retrofit's execute() method to make a synchronous call to the API. The execute() line and val response = chain.proceed(chain.request()) crashes my app if the API service is down or if it's taking too long to retrieve results. I get the java.net.SocketTimeoutException error.
What can I do to prevent my app from crashing when the API service I'm using is down? What can I add to the Interceptor or should I surround my execute() call in a try catch statement?
Proper solution would be to use enqueue instead of execute. synchronous network calls are almost always a bad idea, because you don't want to block the calling thread. to use enqueue you should do
call.enqueue(object : Callback<SomeResponse> {
override fun onFailure(call: Call<SomeResponse>?, t: Throwable?) {
// This code will be called when your network call fails with some exception
// SocketTimeOutException etc
}
override fun onResponse(call: Call<SomeResponse>?, response: Response<SomeResponse>?) {
// This code will be called when response is received from network
// response can be anything 200, 504 etc
}
})
If you must use execute then at the very least you will have to enclose your execute call in try catch
try{
call.execute()
}
catch(e: Exception){
// Process any received exception (SocketTimeOutEtc)
}
I'm did one app which is consuming one api. I can search dogs by breed using this endpoint:
https://dog.ceo/api/breed/labrador/images where {labrador} is the breed.
The app works fine when there is internet connection or wifi. But when there is no internet, the retrofit library is throwing an exception:
Process: com.example.retrofitrecyclerviewkotlin, PID: 11879
javax.net.ssl.SSLHandshakeException: SSL handshake aborted: ssl=0xa4b28280: I/O error during system call, Connection reset by peer
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:362)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:336)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.java:300)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:185)
at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.java:224)
at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.java:108)
at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.java:88)
at okhttp3.internal.connection.Transmitter.newExchange(Transmitter.java:169)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:41)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:94)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:88)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:229)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:172)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
This is my code that where I should fix this problem:
private fun getRetrofit():Retrofit {
return Retrofit.Builder()
.baseUrl("https://dog.ceo/api/breed/")
.addConverterFactory(GsonConverterFactory.create()) //convert the json to DogResponse
.build()
}
private fun searchByRaza(query:String){
CoroutineScope(Dispatchers.IO).launch {
val call = getRetrofit().create(ApiService::class.java).getDogsByRaza("$query/images")
var perros = call.body()
runOnUiThread {
if (call.isSuccessful) {
val images = perros?.images ?: emptyList()
dogImages.clear()
dogImages.addAll(images)
adapter.notifyDataSetChanged()
} else {
showError()
}
}
}
}
I think that the problem is inside of the method getRetrofit() I tried to add one try-catch to handle the exception but I only got erros:
private fun getRetrofit():Retrofit {
try {
return Retrofit.Builder()
.baseUrl("https://dog.ceo/api/breed/")
.addConverterFactory(GsonConverterFactory.create()) //convertir el json a DogResponse
.build()
}catch (e: Exception){
return null
}
}
It says: Null can not be a value of a non-null type Retrofit Any idea to fix this problem guys I will appreciate it.
I would like to show a message to the user alerting about of the lack of internet connection.
thanks so much.
You can use try/catch block in order to catch exception.
CoroutineScope(Dispatchers.IO).launch {
val call = getRetrofit().create(ApiService::class.java).getDogsByRaza("$query/images")
try {
var perros = call.body()
} catch (exc: Exception) {
withContext(Dispatchers.Main) {
Log.d(TAG, "test: "+exc)
//show toast in main thread
}
}
}
I have something similar with another API, Maybe you need to add the .execute() at the end of var perros. Like this:
val call = getRetrofit().create(APIService::class.java).getCharacterByName("$query").execute()
and add a casting
val employees = call.body() as EmployeeResponse
I had successfully queried a list of Github issues from a flutter repository using Github Graphql API and Apollo and was able to fetch them to my application. Strangely, I woke up this morning with a HTTP 401 Error, I am unable to understand where the error is coming from and how to catch and correct it.
2021-03-29 07:51:53.590 2532-3799/smartherd.githubissuetracker E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-3
Process: smartherd.githubissuetracker, PID: 2532
com.apollographql.apollo.exception.ApolloHttpException: HTTP 401
at com.apollographql.apollo.internal.interceptor.ApolloParseInterceptor.parse(ApolloParseInterceptor.java:108)
at com.apollographql.apollo.internal.interceptor.ApolloParseInterceptor$1.onResponse(ApolloParseInterceptor.java:53)
at com.apollographql.apollo.internal.interceptor.ApolloServerInterceptor$executeHttpCall$1.onResponse(ApolloServerInterceptor.kt:110)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:203)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
The Request
val token = "MyToKenHere"
val okHttpClient = OkHttpClient.Builder()
.addInterceptor { chain: Interceptor.Chain ->
val original: Request = chain.request()
val builder: Request.Builder =
original.newBuilder().method(original.method(), original.body())
builder.header("Authorization", "bearer $token")
chain.proceed(builder.build())
}
.build()
val apolloClient: ApolloClient = ApolloClient.builder()
.serverUrl("https://api.github.com/graphql")
.okHttpClient(okHttpClient)
.build()
val results = apolloClient.query(LoadgitQuery()).await()
val theget = results.data?.organization?.repository?.issues?.nodes
arrayList_details.clear()
theget?.forEachIndexed { index, value ->
}
As #Rajasekaran M suggested, I checked and turned out I had to create a new token.
It turns out that sometimes you may (mistakenly) publish sensitive info like GitHub token to some repo. I know, it is a silly mistake but sometimes it happens for all of us.
And from what I noticed, as soon as GitHub sees that you published the token (not sure how it is checked though) it becomes invalid.
val apolloClient= ApolloClient.Builder()
.serverUrl("YourDamin")
.addHttpHeader("Accept","application/json")
.addHttpHeader(AUTHORIZATION, token!!)
.build()
lifecycleScope.launchWhenResumed {
try {
val response = apolloClient.query(UserQuery()).execute()
if (response.hasErrors()) {
} else {
// response==>
}
}catch (e:com.apollographql.apollo3.exception.ApolloHttpException){
try {
if (e.statusCode == 401) {
// true
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
}
use this code for apollo3
I am using two kind of interceptor, one is HttpLoggingInterceptor and another one is my custom AuthorizationInterceptor
I am using below updated retrofit version library,
def retrofit_version = "2.7.2"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'
implementation 'com.squareup.okhttp3:okhttp:4.4.0'
below is code
private fun makeOkHttpClient(): OkHttpClient {
val logger = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
return OkHttpClient.Builder()
.addInterceptor(AuthorizationInterceptor(context)) <---- To put Authorization Barrier
.addInterceptor(logger) <---- To log Http request and response
.followRedirects(false)
.connectTimeout(50, TimeUnit.SECONDS)
.readTimeout(50, TimeUnit.SECONDS)
.writeTimeout(50, TimeUnit.SECONDS)
.build()
}
When I try to execute below code, in file named SynchronizationManager.kt, it gives me an error.
var rulesResourcesServices = RetrofitInstance(context).buildService(RulesResourcesServices::class.java)
val response = rulesResourcesServices.getConfigFile(file).execute() <---In this line I am getting an exception... (which is at SynchronizationManager.kt:185)
My RulesResourcesServices class is here
After debug I found that when below function called, at that time I am getting an exception
#GET("users/me/configfile")
fun getConfigFile(#Query("type") type: String): Call<ResponseBody>
I am getting following error
java.lang.IllegalStateException: closed
at okio.RealBufferedSource.read(RealBufferedSource.kt:184)
at okio.ForwardingSource.read(ForwardingSource.kt:29)
at retrofit2.OkHttpCall$ExceptionCatchingResponseBody$1.read(OkHttpCall.java:288)
at okio.RealBufferedSource.readAll(RealBufferedSource.kt:293)
at retrofit2.Utils.buffer(Utils.java:316)<------- ANDROID IS HIGH-LIGHTING
at retrofit2.BuiltInConverters$BufferingResponseBodyConverter.convert(BuiltInConverters.java:103)
at retrofit2.BuiltInConverters$BufferingResponseBodyConverter.convert(BuiltInConverters.java:96)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:225)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:188)
at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall.execute(DefaultCallAdapterFactory.java:97)
at android.onetap.SynchronizationManager.downloadFile(SynchronizationManager.kt:185)
at android.base.repository.LoginRepository.downloadConfigFilesAndLocalLogin(LoginRepository.kt:349)
at android.base.repository.LoginRepository.access$downloadConfigFilesAndLocalLogin(LoginRepository.kt:48)
at android.base.repository.LoginRepository$loginTask$2.onSRPLoginComplete(LoginRepository.kt:210)
at android.base.repository.LoginRepository$performSyncLogin$srpLogin$1$1.onSRPLogin(LoginRepository.kt:478)
at android.srp.SRPManager$SRPLoginOperation$execute$1.invokeSuspend(SRPManager.kt:323)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:561)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:727)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:667)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:655)
Below is screenshot, in that you can see that, I am getting output of file but don't know why it is throwing an exception.
checked Retrofit's Utils class
https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit2/Utils.java
static ResponseBody buffer(final ResponseBody body) throws IOException {
Buffer buffer = new Buffer();
body.source().readAll(buffer); <-This line throws an error.
return ResponseBody.create(body.contentType(), body.contentLength(), buffer);
}
Update
Same thing is working fine with enqueue method.
response.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(call: Call<ResponseBody?>, response: retrofit2.Response<ResponseBody?>) {
}
})
I have post same issue with Retrofit team, lets see.
https://github.com/square/retrofit/issues/3336
Thanks to JakeWharton (https://github.com/square/retrofit/issues/3336), I can be able to get solution.
Actually in my custom interceptor I was reading response by following code
Response.body().string()
I was doing because above code was helping me to find out that if there is any error than what kind of error it is....
if it is AUTH_ERROR, I have to generate new token and append it to request header.
According to retrofit document, if we call any of below method then response will be closed, which means it's not available to consume by the normal Retrofit internals.
Response.close()
Response.body().close()
Response.body().source().close()
Response.body().charStream().close()
Response.body().byteStream().close()
Response.body().bytes()
Response.body().string()
So to read data, I will use
response.peekBody(2048).string()
instead of
response.body().string(),
so it will not close response.
below is the final code
val response = chain.proceed(request)
val body = response.peekBody(Long.MAX_VALUE).string()//<---- Change
try {
if (response.isSuccessful) {
if (body.contains("status")) {
val jsonObject = JSONObject(body)
val status = jsonObject.optInt("status")
Timber.d("Status = $status")
if (status != null && status == 0) {
val errorCode = jsonObject.getJSONObject("data").optString("error_code")
if (errorCode != null) {
addRefreshTokenToRequest(request)
return chain.proceed(request)
}
}
} else {
Timber.d("Body is not containing status, might be not valid GSON")
}
}
Timber.d("End")
} catch (e: Exception) {
e.printStackTrace()
Timber.d("Error")
}
return response
Extending #Siddhpura Amit's answer:
If you don't know the bytes to pass into peak method then you can still use all of the methods, but will just have to create new Response object.
Inside interceptor:
okhttp3.Response response = chain.proceed(request);
String responseBodyString = response.body().string();
//Do whatever you want with the above string
ResponseBody body = ResponseBody.create(response.body().contentType(), responseBodyString);
return response.newBuilder().body(body).build();
maybe you closed your response in your AuthorizationInterceptor like this
override fun intercept(chain: Interceptor.Chain): Response {
...
val response = chain.proceed(builder.build())
response.close()
...
}
I'm using a custom Interceptor along with Retrofit client in my Android app, that throws an Exception under some specific circumstances. I'm trying to make it work using Kotlin coroutines.
The problem is that I'm unable to handle the before mentioned error, since in the moment the exception is thrown from within the Interceptor instance, it crashes the whole app instead of being caught in the coroutine's try/catch statement. While I was using the Rx implementation, the exception was flawlessly propagated to the onError callback where I was able to handle it the way I needed.
I guess this is somehow related to the underlying threads that are being used for the network call, please see the logs below from the place where the call is made, from the interceptor just before throwing the exception, and the stacktrace:
2019-11-04 17:17:34.515 29549-29729/com.app W/TAG: Running thread: DefaultDispatcher-worker-1
2019-11-04 17:17:45.911 29549-29834/com.app W/TAG: Interceptor thread: OkHttp https://some.endpoint.com/...
2019-11-04 17:17:45.917 29549-29834/com.app E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
Process: com.app, PID: 29549
com.app.IllegalStateException: Passed refresh token can\'t be used for refreshing the token.
at com.app.net.AuthInterceptor.intercept(AuthInterceptor.kt:33)
What am I supposed to do in order to be able to catch and handle this exception from the Interceptor correctly? Am I missing something?
You should subclass IOException and use that to send information from your interceptors to your calling code.
We consider other exceptions like IllegalStateException to be application crashes and do not send them over thread boundaries because we don’t want to burden most callers with catching them.
You may catch the exception in your custom Interceptor and return an empty response with some specific message and code. I have implemented a custom Interceptor to handle the situation like when you do not have or slow internet connection etc... Actually coroutine's suspend functions throws exception when dealing with network calls. In my experience, you can follow 2 approaches. 1. wrap your all network call in try...catch or 2. create a custom Interceptor and handle exceptions there and return some specific response.
Approach 1:
try {
webservice.login(username, password)
} catch (e: Exception) {
//...
}
Approach 2:
Create a custom Interceptor and handle exception there.
class LoggingInterceptor : Interceptor {
#Throws(Exception::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
try {
val response = chain.proceed(request)
val bodyString = response.body()!!.string()
return response.newBuilder()
.body(ResponseBody.create(response.body()?.contentType(), bodyString))
.build()
} catch (e: Exception) {
e.printStackTrace()
var msg = ""
when (e) {
is SocketTimeoutException -> {
msg = "Timeout - Please check your internet connection"
}
is UnknownHostException -> {
msg = "Unable to make a connection. Please check your internet"
}
is ConnectionShutdownException -> {
msg = "Connection shutdown. Please check your internet"
}
is IOException -> {
msg = "Server is unreachable, please try again later."
}
is IllegalStateException -> {
msg = "${e.message}"
}
else -> {
msg = "${e.message}"
}
}
return Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(999)
.message(msg)
.body(ResponseBody.create(null, "{${e}}")).build()
}
}
}
I have created gist for complete implementation of LoggingInterceptor with print logs of request and response. LoggingInterceptor
I dont know what exactly you need, but understood like this:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
okhttp3.Response response = chain.proceed(request);
// todo deal with the issues the way you need to
if (response.code() == SomeCode) {
//do something
return response;
}
return response;
}
})
.build();
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(url)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();