square / square/wire

Global request interceptor

Open
#2,769 10 comments 0 reactions 1 assignee View on GitHub

@oldergod is already working on this.

Since Feb 7, 2024.

Dominant language
Kotlin
Stars
4.4k
Forks
627
Avg merge
3d 15m
Merged PRs (30d)
20

Description

I couldn't find a way in Wire to intercept all the requests and handle their responses. Without that implementing things like authentication is much harder.

The way it works in io.grpc is that you implement and add a io.grpc.ClientInterceptor when creating a io.grpc.Channel. For example, the code authenticating requests might look like this (note that the existing auth token is invalidated on receiving Status.UNAUTHENTICATED from the server):

class GrpcAuthInterceptor (private val authTokenHolder: AuthTokenHolder) : ClientInterceptor {
    override fun <ReqT : Any, RespT : Any> interceptCall(
        method: MethodDescriptor<ReqT, RespT>,
        callOptions: CallOptions,
        next: Channel
    ): ClientCall<ReqT, RespT> = object : ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
        next.newCall<ReqT, RespT>(method, callOptions)
    ) {
        override fun start(responseListener: Listener<RespT>, headers: Metadata) {
            val needsAuth = headers.needsAuth()
            if (!needsAuth) return super.start(responseListener, headers)

            val authToken = runBlocking { authTokenHolder.getAuthToken() }
            if (authToken != null) {
                headers.authenticate(authToken)
            }
            super.start(
                if (authToken != null) responseListener(authToken, responseListener) else responseListener,
                headers
            )
        }
    }

    private fun <RespT : Any> responseListener(
        authToken: String,
        original: ClientCall.Listener<RespT>
    ) = object : ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(original) {
        override fun onClose(status: Status, trailers: Metadata) {
            super.onClose(status, trailers)
            if (status == Status.UNAUTHENTICATED) {
                runBlocking {
                    authTokenHolder.invalidate(authToken)
                }
            }
        }
    }
}

However, it's hard to do the same in Wire as there is no single point of request handling.

Here it was suggested to add an OkHttpClient interceptor and set the HTTP headers instead which IMO is not correct as it's a wrong application layer (HTTP vs gRPC). Moreover, it's hard to detect authentication errors as gRPC has its own status codes and always returns HTTP status code 200 (even if the request couldn't be authenticated according to gRPC, for example). This of course could be worked around by checking the grpc-status header but it doesn't look clean to me.

Am I missing something? Or how are you supposed to implement authentication (or any other generic request handling) in Wire?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.