I am facing a very strange issue in my Android app with OkHttp3 Interceptor where I set different logging levels to different build variants.
I have setup an OkHttp3 Interceptor in my app to intercept API requests, and if any API returns 401 error, the interceptor gets the refreshed token from backend and updates the header of original request with the new token and repeats it. Let's call this RefreshTokenInterceptor. I add this interceptor when building my OKHttpClient. Besides this, I also add an HttpLoggingInterceptor to log the API requests and responses or stop logging in case of release builds. Let's call this LoggingInterceptor
Here is the code where I build my OkHttpClient. This code should be enough because the refresh mechanism works fine if the logging level is changed.
val loggingInterceptor = HttpLoggingInterceptor()
// changing level to Level.BODY in the below solves the issue,
// but I don't want to log the results
when {
BuildConfig.DEBUG -> loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
else -> loggingInterceptor.level = HttpLoggingInterceptor.Level.NONE
}
val httpClientBuilder = OkHttpClient.Builder()
.readTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(WRITE_TIMEOUT, TimeUnit.MILLISECONDS)
.addInterceptor(ConnectivityInterceptor())
.addInterceptor(TokenRefreshInterceptor())
.addInterceptor(loggingInterceptor)
.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
When I set the logging level of the LoggingInterceptor in both cases to Level.BODY, everything works fine. But in case of logging level set to Level.NONE, the token refresh mechanism stops working.
To be more specific, in this case, this is what happens in RefreshTokenInterceptor: when a request returns 401, the refresh call is made, but nothing happens afterwards. No success or failure case gets called (Maybe the interceptor Chain breaks but who knows).
Here is what I have tried so far
Removing the LoggingInterceptor altogether -> does not work
Setting logging level to Level.NONE for all build variants -> does
not work
Setting logging level to Level.BODY for all build variants -> works
like magic
I also searched a lot today on this, but could not find any link between logging levels messing up other interceptors. Any help would be appreciated, and if you need more code, I can post it.
The problem was being caused by a memory leak which happened because an OkHttp3 Response object in the TokenRefreshInterceptor which was not being closed. I got to know about this leak on Firebase Console. Closing this Response after consuming it solved the issue. But I'm still unsure why it worked fine when I enabled body-level logging.
Related
We've been using okhttpClient in our android app, and noticed that OkHttpClient lib is quietly retrying requests, with the config retryOnConnectionFailure boolean in OkHttpClient class.
Is there any way to log the retries? is there a callback or lambda we can use?
I looked into the source code of okhttpClient and did some research, but was not lucky, didn't find anything.
You log requests via interceptor.
in your *.gradle add
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'
and to your OK client:
httpClient.addNetworkInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
Now, there are 2 places to hook an interceptor, addInterceptor and addNetworkInterceptor. The difference is that regular interceptor shows all requests, including ones served from local cache and network interceptors shows only traffic to a server.
Using
com.squareup.retrofit2:retrofit:2.9.0
com.squareup.okhttp3:okhttp:4.4.0
com.google.firebase:firebase-perf-ktx (bom version is 29.0.4)
I'm unable to see any network requests inside the Firebase Performance network requests section. Custom traces work fine but the network requests are empty. I also cannot see any I/FirebasePerformance logcat logs suggesting that http requests are being logged.
The app uses a simple retrofit service to make requests which looks like:
Retrofit.Builder()
.baseUrl("https://myurl.com")
.client(get())
.addConverterFactory(MoshiConverterFactory.create(get()))
.build()
.create(MyService::class.java)
The OkHttpClient :
OkHttpClient.Builder()
.addInterceptor(UserAgentHeaderInterceptor())
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()
I have already tried the following:
Been through Firebase's trouble shooting guide for this issue.
Downgraded my okHttp version to 3.12.
My app is using OkHttp 3 and Retrofit2 for networking. It communicates with server, that returns session after successful login. I need to save this session and attach it to request header for all requests that will be executed after login.
I have used Cookie Jar for this and it worked just fine on emulator and my Samsung phone, but my client reported problem on Huawei P40 Lite. After some log checking on server, I figured that session wasnt sent to server and it caused verification problem.
After that I used Huawei live debuging to test my app on remote Huawei P40 device and reported problem persisted. After some playing around with my app and testing I concluded that OkHttp 3 Cookie Jar and Interceptor that i use to handle saving and attaching session were not working on Huawei at all. Could this be related to Google services ban for newest Huawei devices as app is working fine on older ones like P30?
Any tips or solutions are more than welcome.
I have overcame this by manually extracting session from response and attaching it to my calls with #Header, but this solution is very unclean.
Here is my code for OkHttpClient. Keep in mind it worked and it still is working just fine in production for over 2 years on non newest Huawei phones.
return new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), loadX509TrustManager(tmf))
.hostnameVerifier(hostnameVerifier)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.cookieJar(new JavaNetCookieJar(cookieManager))
.cache(cache)
.addNetworkInterceptor(logInterceptor)
.build();
UPDATE
I was wrong about Cookie Jar and Interceptors not working. I had try catch block with this line: ProviderInstaller.installIfNeeded(context); that would fail because it requires google services, and in catch blok I would retrun a OkHttpClient that has no Cookie Jar attached.
I checked if there is a similar Huawei replacement for: ProviderInstaller.installIfNeeded(context); but I couldn't find one. This line is used to update any missing security provider patches if they were missing. Google documentation about mentioned line.
Try another Cookie jar implementation? Perhaps this persistent one? There might be a bad CookieManager on the P40.
Analyzing some previously written code I have some questions concerning the set up with OkHttpClient. We did create a single OkHttpClient instance and reuse it for all of our HTTP calls. We execute REST API calls and caching is not needed.
However I do see some code in an interceptor
request = request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=${120}").build()
Question 1: Would this have any effect if caching is not enabled?
Secondly there is one API call that fetches configuration data and I feel it can benefit from caching.
Question 2: Can we enable caching for just one call, say, if we customize the client using newBuilder()?
It could have an effect on caches between yourself and the remote service. But this specific interceptor seems designed to force the request to only use the local cache, and not attempt to use the network.
https://square.github.io/okhttp/4.x/okhttp/okhttp3/-cache-control/only-if-cached/
To rephrase my question slightly ...if no cache is set on the original call then the request with the cache-control will not do anything right?
Typically you would not need an interceptor, instead you would apply the cache settings to the request.
https://square.github.io/okhttp/4.x/okhttp/okhttp3/-cache/
Request request = new Request.Builder()
.cacheControl(new CacheControl.Builder().noCache().build())
.url("http://publicobject.com/helloworld.txt")
.build();
Not sure this addresses the second question. I'm asking if we have set up one single OkHttpClient instance can we have only some requests cached or the fact that we turn on cache for one request turns it on for all other requests onward?
I am building my OkHttp in the application class and holding a static reference to it. This way, I have one instance of OkHttp at any given time.
okHttpClient = new OkHttpClient.Builder()
.cache(new Cache(context.getCacheDir(), 50 * 1024 * 1024))
.addInterceptor(new HttpInterceptor())
.build();
Now, I am making two kind of requests, one requires OAuth2 while the other require the API key and secret supplied as parameters to the request URL. I am using an interceptor for the request that requires an OAuth2.
So the problem is the latter request uses resorts to the interceptor for authentication, thus the request fails. Is there any way I can tell it "See, ignore the interceptor for this request"? Or do I need two instances of OkHttpClient?
You will need two instances since after creating single instance wit interceptor attached to it will not ignore interceptor.
A hack can be to identify the url in the interceptor and ignore interceptor operation for that request.
The cleanest solution is to have a single OkHttp instance, but in your interceptor have the logic to decide how to authenticate.
This example ServiceInterceptor.java checks each request and decides how to authenticate in a pluggable manner. You could do something similar.
There are many benefits of this approach over different clients including
reuse of the executor
possibility that the connections can be coalesced for multiple hosts e.g. www.mysvc.com and api.mysvc.com
Support for redirection across hosts