Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrofit : java.lang.IllegalStateException: closed

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.

enter image description here

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

like image 725
Siddhpura Amit Avatar asked Mar 13 '20 13:03

Siddhpura Amit


2 Answers

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(2048).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
like image 128
Siddhpura Amit Avatar answered Oct 03 '22 09:10

Siddhpura Amit


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();
like image 37
Mohib Irshad Avatar answered Oct 03 '22 09:10

Mohib Irshad