使用 Coroutine runblock 和 Authenticator 处理来自改造的 401 响应

sad*_*dat 5 android okhttp retrofit2 kotlin-coroutines

我正在尝试使用身份验证器来处理 401 响应。我所做的是

fun provideAccessTokenAuthenticator(
    mainApiServiceHolder: MainApiServiceHolder,
    preferences: SharedPreferences
) = object : Authenticator {
    override fun authenticate(route: Route?, response: Response): Request? {
        val accessToken = preferences.getString(ACCESS_TOKEN, null)
        if (!isRequestWithAccessToken(response) || accessToken == null) {
            return null
        }
        synchronized(this) {
            val newAccessToken = preferences.getString(ACCESS_TOKEN, null)!!
            // Access token is refreshed in another thread.
            if (accessToken != newAccessToken) {
                return newRequestWithAccessToken(response.request, newAccessToken)
            }

            // Need to refresh an access token
            val refreshTokenResponse = runBlocking {
                Log.d("zzzzzzzzzz", "refresh token is running")
                mainApiServiceHolder.mainApiService?.refreshToken(
                    "refresh_token",
                    preferences.getString(REFRESH_TOKEN, null)!!,
                    AuthRepository.CLIENT_ID,
                    AuthRepository.CLIENT_SECRET
                )
            }
            Log.d("zzzzzzzzzz", refreshTokenResponse?.body()?.access_token!!)
            return if (refreshTokenResponse?.isSuccessful!!) {
                Log.d("zzzzzzzzzz", "refresh token is successful")
                newRequestWithAccessToken(
                    response.request,
                    refreshTokenResponse.body()?.access_token!!
                )
            } else {
                Log.d("zzzzzzzzzz", "refresh token is unsuccessful")
                response.request.newBuilder().header("Content-Type", "application/json").build()
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

现在,当有 401 响应时它会被调用。刷新令牌调用也会被触发(来自 Log)。但是,它永远不会在 refreshTokenResponse 中获得结果,之后没有任何反应。我认为这是使用 runBlock 的错误方式。api是

@FormUrlEncoded
@POST("/api/auth/token/")
suspend fun refreshToken(
    @Field("grant_type") grant_type: String,
    @Field("refresh_token") refresh_token: String,
    @Field("client_id") client_id: String,
    @Field("client_secret") client_secret: String
): Response<LoginResponse>
Run Code Online (Sandbox Code Playgroud)

任何帮助将非常感激。谢谢

Joh*_*n G 6

在 Retrofit API 中,考虑用同步Call替换异步 runBlocking{} 挂起乐趣。我最幸运的是避免在 Authenticator 中使用协程。

我遇到了同样的问题。令牌请求直接进入了一个黑洞。该应用程序冻结。该请求再也没有出现过。没有错误,没有什么。

但是在应用程序的其他任何地方,暂停的乐趣都恢复得很好。从 ViewModels 到 WorkManager,它每次都有效。但从身份验证器,从来没有。身份验证器出了什么问题?Authenticator 有什么特别之处让它如此行事?

然后我用一个简单的Call替换了 runBlocking{} 协程。这一次,请求回来了,令牌没有大惊小怪地到达。

我让 API 工作的方式如下所示:

@FormUrlEncoded
@POST("token")
fun refreshTokenSync(
    @Field("refresh_token") refreshToken: String,
): Call<RefreshMyTokenResponse>
Run Code Online (Sandbox Code Playgroud)

然后,在身份验证器中:

 val call = API.refreshTokenSync(refreshToken)
 val response = call.execute().body()
Run Code Online (Sandbox Code Playgroud)

我希望这可以帮助遇到同样问题的其他人。您可能会收到来自 Android Studio 的警告,指出这是一个不当的阻塞调用。不要付钱,没关系。

  • 由于 OkHttp 中的 maxRequestsPerHost = 5,runBlocking 可能会在并行或递归请求的情况下导致死锁,请参阅[此问题](https://github.com/square/okhttp/issues/6747)。`Call.execute()` 工作正常。 (2认同)