在 grpc 拦截器中使用 ThreadLocal 的正确方法是什么?

gar*_*ulb 7 java rpc grpc

我们有一个 gPRC 服务,需要在类的 ThreadLocal 变量中设置身份验证/身份信息,才能正确调用另一个服务。gPRC 服务从请求中获取身份验证/身份信息,因此我正在考虑使用拦截器。

首先,我有一些代码如下所示。

public class ImpersonationInterceptor {
    public <ReqT, RespT> interceptCall(
        ServerCall<ReqT, RespT> serverCall, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        Principal principal = ... // get the identify from the request

        AuthContext.setPrincipal(principal); // underneath it uses a ThreadLocal.

        return next.startCall(
            new SimpleForwardingServerCall<>(call) {
                public void close(Status status, Metadata trailers) {
                    AuthContext.setPrincipal(null); // clear the identity
                }
            }
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

问题。

  • 服务方法本身的执行可能与执行拦截器不在同一个线程上,对吗?
  • 如果是真的,上面的方法不起作用,那么问题是,在 gRPC 世界中设置 ThreadLocal 变量的规范方法是什么?我知道 gRPC 从 0.12 开始就支持 Context,但就我而言,我必须使用 AuthContext 的 ThreadLocal 机制。

首先十分感谢。

Eri*_*son 8

对于此类上下文信息,您必须非常小心地使用 ThreadLocals,因为您不想意外地为客户端使用错误的身份。

gRPC 的每个回调都可以发生在不同的线程上,多个 RPC 的回调可以发生在同一线程上。

您需要遵循Contexts.interceptCall()这样的模式。您必须在每次调用后设置/取消设置:

public class ImpersonationInterceptor {
  public <ReqT, RespT> interceptCall(
      ServerCall<ReqT, RespT> serverCall, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
    Principal principal = ...;
    AuthContext.setPrincipal(principal);
    try {
      return new WrappingListener<>(next.startCall(call, headers), principal);
    } finally {
      AuthContext.clearPrincipal();
    }
  }

  private static class WrappingListener<ReqT> extends
      ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT> {
    private final Principal principal;

    public WrappingListener(ServerCall.Listener<ReqT> delegate, Principal principal) {
      super(delegate);
      this.principal = principal;
    }

    @Override
    public void onMessage(ReqT message) {
      AuthContext.setPrincipal(principal);
      try {
        super.onMessage(message);
      } finally {
        AuthContext.clearPrincipal();
      }
    }
    ... repeat for each method
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 拦截器通常直接在调用它们的线程上相互调用。所以对于大多数拦截器来说这不是问题。如果拦截器使用另一个线程来发出回调,则需要确保传播上下文。 (3认同)