I am creating an app using Retrofit 2.1.0 and OkHttp 3.4.2.
In debug mode with minifyEnabled set to false everything works perfectly but as soon as i change minifyEnabled to true i get the following exception:
HTTP FAILED: java.net.ProtocolException: Too many follow-up requests: 21
My Proguard rules for OkHttp are as follows:
-keep class com.squareup.okhttp3.** {
*;
}
-dontwarn okhttp3.**
-dontwarn okio.**
I can't understand why this exception is thrown and i do not understand why the app is seems to be making 21 follow up requests. Can anyone help me?
Typically this happens when you get into a redirect loop, or when you authenticate with the wrong username, and then retry with the same wrong username. You can fix your Authenticator to check if it is a follow-up request and not retry in that case.
OkHttp is an efficient HTTP & HTTP/2 client for Android and Java applications. It comes with advanced features, such as connection pooling (if HTTP/2 isn't available), transparent GZIP compression, and response caching, to avoid the network completely for repeated requests.
I've just had this exact same error: java.net.ProtocolException: Too many follow-up requests: 21
. For your interest I'm using Retrofit 2.5.0 and OkHttp 3.14.1, although versions don't matter much.
I too only have this error with Proguard enabled (actually I'm using R8 but the result is the same). This is important and hints the root cause.
What's the problem? I authenticate using OAuth, adding an "Authorization" header as usual. When the token expires the server sends a 401 Unauthorized. Since I'm using an OkHttp Authenticator
to refresh the token, when a 401 is received the authenticate
method is called.
The problem is that I parse the response of this 401 Unauthorized request using Gson, like this:
override fun authenticate(route: Route?, response: Response): Request? {
val responseError: ResponseError? = response.body()?.let {
Gson().fromJson(it.string(), ResponseError::class.java) // <- Fails!
}
// Check server response and decide if should refresh token...
}
But since the class ResponseError
is obfuscated by proguard it's fields don't match the names of the JSON that the server sends, which makes Gson().fromJson
fail, and consequently the token is not refreshed. The result is that a network call is performed repeatedly until the Exception is thrown.
The fix is trivial. Just add @Keep
to ResponseError
:
import androidx.annotation.Keep
@Keep
data class ResponseError(
val error: String? = null,
val error_description: String? = null
)
Since your problem happens with proguard enabled only, it's likely that a request or response class is being obfuscated, which makes the JSON parsing fail. Or you may be saving some JSON to Shared Preferences using Gson.fromJson
and Gson.fromJson
with an obfuscated class.
To fix the problem add @SerializedName
and/or @Keep
to all your requests and responses (specially enums, which give more trouble). Alternatively you can simply put the all the requests and responses into a single package and exclude it. In addition to requests and responses, pay special attention to any Gson.fromJson
and Gson.fromJson
call too.
To help diagnose the problem you can check what proguard/R8 does by checking the mappings file you'll find in app/build/outputs/mapping/release/mapping.txt
(for a release build). It contains all the transformations that proguard/R8 has done to your code.
You can also analyze the APK by doing Build -> Analyze APK... You'll see which classes are being obfuscated in a very simple way. You can analyze 2 APKs at the same time (one minified and one not minified) and compare them.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With