【问题标题】:What is the right way to use ThreadLocal in grpc interceptor?在 grpc 拦截器中使用 ThreadLocal 的正确方法是什么?
【发布时间】:2019-11-12 03:53:02
【问题描述】:

我们有一个 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
                }
            }
        )
    }
}

问题。

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

非常感谢。

【问题讨论】:

    标签: java rpc grpc


    【解决方案1】:

    对于此类上下文信息,您必须非常小心使用 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
      }
    }
    

    【讨论】:

    • 感谢您的回复和澄清@EricAnderson。你解释的对我来说很有意义。另一个愚蠢的问题,这些回调的执行与服务方法的实际执行有什么关系,因为后者是我关心 ThreadLocal 的使用方式。另外,我可以按照github.com/grpc/grpc-java/blob/master/examples/src/test/java/io/… 为正确的行为编写单元测试吗?
    • 服务方法的执行由 io.grpc.stub.ServerCalls 决定。根据方法的类型和 ServerCalls 中的具体实现,可以在 startCall()、onMessage()、onHalfClose() 期间调用应用程序。根据服务代码,它也可以在 onCancel() 和 onReady() 期间调用。将来 onComplete() 可以调用该服务。因此,本质上,您需要为每个方法设置主体。
    • @EricAnderson - 关于“来自 gRPC 的每个回调都可能发生在不同的线程上”,假设您有 3 个链式拦截器。是否保证在同一个线程上调用给定回调的完整拦截器链?否则我不明白这是如何工作的......你必须以某种方式在每个拦截器中传播主体......?
    • 拦截器通常直接在它们被调用的线程上相互调用。所以对于大多数拦截器来说这不是问题。如果拦截器使用另一个线程发出回调,则需要确保 Context 被传播。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-07
    • 1970-01-01
    • 2016-06-11
    • 1970-01-01
    • 2021-05-11
    • 2011-12-01
    • 2014-02-13
    相关资源
    最近更新 更多