Reputation: 1333
I need to add freshCsrf
string to each request into form body. Uses OkHttpClient
with AuthInterceptor
. The problem is how to send request inside interceptor in order to get the freshCsrf
string?
In code below occurs error
Suspend function 'getAuth' should be called only from a coroutine or another suspend function
class AuthInterceptor(val authApi: IAuthService) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val freshCsrf = authApi.getAuth().freshCsrf
// ... add freshCsrf to form body
return chain.proceed(request)
}
}
// Retrofit service
interface IAuthApi {
@GET("/auth")
suspend fun getAuth(): Auth
}
I try to use coroutine, but also failed because can't wait result. One of example with async/await
case ended with error
Suspend function 'await' should be called only from a coroutine or another suspend function
private val scope = CoroutineScope(Dispatchers.IO)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val freshCsrf = scope.async { authApi.getAuth().freshCsrf }
freshCsrf.await()
// ... add freshCsrf to form body
return chain.proceed(request)
}
I confused with runBlocking
description
It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.
In one side, retrofit interface is suspending and I need to wait network result in order to continue creating other request (bridge - ok). But in the other side, it isn't main function or test. And many tutorials and articles tell that must to avoid runBlocking
in production code. Can you explain it?
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val freshCsrf = runBlocking {
return@runBlocking authApi.getAuth().freshCsrf
}
// ... add freshCsrf to form body
return chain.proceed(request)
}
Upvotes: 2
Views: 5911
Reputation: 39
interface ApiInterface {
@GET("user/list")
fun userListGet(): Call<UserPageResponse>
}
lifecycleScope.launch {
val response = apiInterface.userListGet().await()
}
Upvotes: 0
Reputation: 51
If you don't want to use runBlocking
then you will have to introduce second endpoint that returns Call<Auth>
in the IAuthApi interface. Call<Auth>
will allow you to run execute()
function on it to run a request in synchronous (blocking) way.
Below how it can look like using Call.execute
:
class AuthInterceptor(val authApi: IAuthService) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val freshCrsf = authApi.getAuthCall().execute().let { authResponse ->
if(authResponse.isSuccessful) {
authResponse.body().freshCrsf
} else {
// TODO handle error for example you can read authResponse.errorBody()
// and do something based on it
}
}
// ... add freshCsrf to form body
return chain.proceed(request)
}
}
// Retrofit service
interface IAuthApi {
@GET("/auth")
fun getAuthCall(): Call<Auth>
@GET("/auth")
suspend fun getAuth(): Auth
}
I think runBlocking
in this case is not a bad option really. As documentation state it's used to bridge suspending code to the blocking one. Main function and tests for me are common use cases.
Upvotes: 5
Reputation: 76807
Use .execute()
instead of .enqueue()
and it will perform the request synchronously.
Interceptor
is not required, as CSRF protection usually resides in the HMTL <head>
;
this would be something else, if the string would be served in the HTTP headers.
The relevent documentation: Interface Call<T>
.
Upvotes: 4