I'm evaluating a migration from Wire to kotlinx-rp...
# kotlinx-rpc
m
I'm evaluating a migration from Wire to kotlinx-rpc at the moment, and am trying to find the equivalent of GrpcCall#requestMetadata. I am aware of client interceptors in the gRPC flavor of kotlinx-rpc, but I don't know the auth credentials at the time when the GrpcClient is created. Therefore, I'd rather attach the auth credentials just before making the call on my GrpcService object. I found GrpcCallOptions and GrpcCallCredentials, but cannot figure out how to use it at the call site. Is that possible?
a
Hi! Can you please provide a code on how you would do that with Wire?
m
Thanks for your message! Of course, no problem. Wall of text incoming. 👀 Here is a generalized block that illustrates the question. In Wire gRPC, the
GrpcCall
itself is an object created from the service with its own
requestMetadata
. The call is then "executed" with the request payload. I suppose that OkHttp's
Call
interface may have been the role model for it.
Copy code
// Wire:

// Declare gRPC service somewhere
val grpcClient = GrpcClient.Builder().build()
val service = grpcClient.create(MyService::class)

// Create a "call" object
val call = service.SomeMethod()

// Prepare request and generate an auth key at the call site
val request = SomeRequest(device_id = "test")
val authKey = calculateAuthKey()

// Attach call-specific headers as metadata
call.requestMetadata += mapOf("x-auth-key" to authKey)

// Execute the call and return the response
val response = call.execute(request)
return response
In kotlinx-rpc, the request is provided directly to the service without a "Call-type object" in the middle. Metadata can be given exclusively through the
GrpcClient
configuration, but I can't influence that at the call site.
Copy code
// kotlinx-rpc:

// Declare gRPC service somewhere
val grpcClient = GrpcClient(...)
val service = grpcClient.withService<MyService>()

// Prepare request and generate an auth key at the call site
val request = SomeRequest { this.deviceId = "test" }
val authKey = calculateAuthKey()

// Q: How to attach call-specific headers as metadata?

// Execute the call and return response
val response = service.SomeMethod(request)
return response
Since posting my question, I have experimented with
GrpcClientInterceptor
and its
callOptions.callCredentials
setter. Since the
GrpcCallCredentials
interface method has a suspending context, I'm trying to grab the relevant info from a coroutine context element. Something like the below. Is that encouraged or a hard no from your POV?
Copy code
// Define a coroutine context element that stores/provides the auth key...
class AuthContext(val authKey: String) : AbstractCoroutineContextElement(AuthContext) {
    companion object : CoroutineContext.Key<AuthContext>
}

// ...and the callCredentials to attach to the GrpcClient
class MyCallCredentials : GrpcCallCredentials {
    override suspend fun GrpcCallCredentials.Context.getRequestMetadata(): GrpcMetadata {
        // Grab coroutine context element, if it's there
        val auth = currentCoroutineContext()[AuthContext]

        return GrpcMetadata {
            auth?.let {
                // Attach if present
                append("x-auth-key", it.authKey)
            }
        }
    }
}

// At the call site, provide the context element around the authenticated call
val response = withContext(AuthContext(authKey)) {
    service.SomeMethod(request)
}
return response
Looking forward to your feedback. Thanks again!
👀 1