目录
一、Header传值
    1.客户端实现拦截器
    2.客户端注入拦截器
    3.服务端实现拦截器
    4.服务端注入拦截器
    5.输出信息
二、错误信息处理
    1.服务端设置错误信息
    2.BlockingStub获取错误信息
    3.FutureStub-Direct获取错误信息
    4.FutureStub-Callback获取错误信息
    5.asyncCall获取错误信息
    6.advancedAsyncCall获取错误信息
    7.异常信息抽取
三、示例代码
四、系列文章

上篇中分析了gPRC支持的四种类型示例,本文继续示例解读,Header传值、错误处理。

一、Header传值

在RPC的服务调用中,往往需要在链路中通过透传一些值。gRPC同样提供了通过Header透传元数据新信息。

1.客户端实现拦截器

客户端拦截器需要实现ClientInterceptor接口,看以下示例.

public classHeaderClientInterceptorimplementsClientInterceptor{
private static final Logger logger = Logger.getLogger(HeaderClientInterceptor.class.getName());

@VisibleForTesting
static final Metadata.Key<String> CUSTOM_HEADER_KEY =
  Metadata.Key.of("custom_client_header_key", Metadata.ASCII_STRING_MARSHALLER);

@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
  CallOptions callOptions, Channel next) {
return new SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {

  @Override
  publicvoidstart(Listener<RespT> responseListener, Metadata headers) {
    // @1 在Header中设置需要透传的值
    headers.put(CUSTOM_HEADER_KEY, "customRequestValue");
    super.start(new SimpleForwardingClientCallListener<RespT>(responseListener) {
      @Override
      publicvoidonHeaders(Metadata headers) {
      // @2 获取从服务端返回的Header信息
        logger.info("header received from server:" + headers);
        super.onHeaders(headers);
      }
    }, headers);
  }
};
}
}
2.客户端注入拦截器

将拦截器注入到gRPC服务中,见示例。

privateCustomHeaderClient(String host, int port) {
originChannel = ManagedChannelBuilder.forAddress(host, port)
    .usePlaintext()
    .build();
ClientInterceptor interceptor = new HeaderClientInterceptor();
// @1 构建Channel时注入客户端拦截器
Channel channel = ClientInterceptors.intercept(originChannel, interceptor);
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
3.服务端实现拦截器

服务端拦截器需实现ServerInterceptor接口,见示例。

public classHeaderServerInterceptorimplementsServerInterceptor{

  private static final Logger logger = Logger.getLogger(HeaderServerInterceptor.class.getName());

  @VisibleForTesting
  static final Metadata.Key<String> CUSTOM_HEADER_KEY =
      Metadata.Key.of("custom_server_header_key", Metadata.ASCII_STRING_MARSHALLER);


  @Override
  public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
      ServerCall<ReqT, RespT> call,
      final Metadata requestHeaders,
      ServerCallHandler<ReqT, RespT> next) {
    // @1 打印从客户端设置的header信息
    logger.info("header received from client:" + requestHeaders);
    return next.startCall(new SimpleForwardingServerCall<ReqT, RespT>(call) {
      @Override
      publicvoidsendHeaders(Metadata responseHeaders) {
        // @2 响应客户端设置服务端Header信息
        responseHeaders.put(CUSTOM_HEADER_KEY, "customRespondValue");
        super.sendHeaders(responseHeaders);
      }
    }, requestHeaders);
  }
}
4.服务端注入拦截器
privatevoidstart() throws IOException {
    server = ServerBuilder.forPort(PORT)
        // @1 构建Server时注入自定义拦截器
        .addService(ServerInterceptors.intercept(new GreeterImpl(), new HeaderServerInterceptor()))
        .build()
        .start();
    logger.info("Server started, listening on " + PORT);

  }
5.输出信息
// @1 Server输出
header received from client:Metadata(content-type=application/grpc,user-agent=grpc-java-netty/1.25.0,custom_client_header_key=customRequestValue,grpc-accept-encoding=gzip)
// @2 client输出
header received from server:Metadata(content-type=application/grpc,custom_server_header_key=customRespondValue,grpc-encoding=identity,grpc-accept-encoding=gzip)

小结:gRPC上下游Header传值通过客户端实现ClientInterceptor和服务端实现ServerInterceptor来实现,并在client和server构造时注入拦截器。

二、错误信息处理

当Server抛出错误时,需要将错误信息返回给Client调用方,同时可以自定义错误信息;gRPC提供了相关方法。

1.服务端设置错误信息
private static final Metadata.Key<DebugInfo> DEBUG_INFO_TRAILER_KEY =
      ProtoUtils.keyForProto(DebugInfo.getDefaultInstance());

private static final DebugInfo DEBUG_INFO =
DebugInfo.newBuilder()
  .addStackEntries("stack_entry_1")
  .addStackEntries("stack_entry_2")
  .addStackEntries("stack_entry_3")
  .setDetail("detailed error info.").build();
private static final String DEBUG_DESC = "detailed error description";
Server server = ServerBuilder.forPort(0).addService(new GreeterGrpc.GreeterImplBase() {
  @Override
  publicvoidsayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
    // @1 自定义错误信息通过Metadata设置
    Metadata trailers = new Metadata();
    trailers.put(DEBUG_INFO_TRAILER_KEY, DEBUG_INFO);
    // @2 Server通过onError将错误信息传输给Client
    responseObserver.onError(Status.INTERNAL.withDescription(DEBUG_DESC)
        .asRuntimeException(trailers));
    // @3 通过asRuntimeException将自定义的异常信息传给Client
  }
}).build().start();
2.BlockingStub获取错误信息
voidblockingCall() {
    GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);
    try {
      stub.sayHello(HelloRequest.newBuilder().build());
    } catch (Exception e) {
     // @1 接受Server端返回的异常信息
      verifyErrorReply(e);
    }
}
3.FutureStub-Direct获取错误信息
voidfutureCallDirect() {
    GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel);
    ListenableFuture<HelloReply> response =
        stub.sayHello(HelloRequest.newBuilder().build());

    try {
      response.get();
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new RuntimeException(e);
    } catch (ExecutionException e) {
      // @1 此处解析Server端传输的异常信息
      verifyErrorReply(e.getCause());
    }
}
4.FutureStub-Callback获取错误信息
voidfutureCallCallback() {
GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel);
ListenableFuture<HelloReply> response =
    stub.sayHello(HelloRequest.newBuilder().build());
final CountDownLatch latch = new CountDownLatch(1);
Futures.addCallback(
    response,
    new FutureCallback<HelloReply>() {
      @Override
      publicvoidonSuccess(@Nullable HelloReply result) {
      }

      @Override
      publicvoidonFailure(Throwable t) {
        // @1 此处解析Server端传输的异常信息
        verifyErrorReply(t);
        latch.countDown();
      }
    },
    directExecutor());
if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) {
  throw new RuntimeException("timeout!");
}
}
5.asyncCall获取错误信息
voidasyncCall() {
GreeterStub stub = GreeterGrpc.newStub(channel);
HelloRequest request = HelloRequest.newBuilder().build();
final CountDownLatch latch = new CountDownLatch(1);
StreamObserver<HelloReply> responseObserver = new StreamObserver<HelloReply>() {

  @Override
  publicvoidonNext(HelloReply value) {
  }

  @Override
  publicvoidonError(Throwable t) {
   // @1 处理Server返回的异常信息
    verifyErrorReply(t);
    latch.countDown();
  }

  @Override
  publicvoidonCompleted() {
  }
};
stub.sayHello(request, responseObserver);

if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) {
  throw new RuntimeException("timeout!");
}
}
6.advancedAsyncCall获取错误信息
void advancedAsyncCall() {
ClientCall<HelloRequest, HelloReply> call =
    channel.newCall(GreeterGrpc.getSayHelloMethod(), CallOptions.DEFAULT);

final CountDownLatch latch = new CountDownLatch(1);

call.start(new ClientCall.Listener<HelloReply>() {

  @Override
  public void onClose(Status status, Metadata trailers) {
    // @1 处理Server端返回的异常信息
    Verify.verify(status.getCode() == Status.Code.INTERNAL);
    Verify.verify(trailers.containsKey(DEBUG_INFO_TRAILER_KEY));
    try {
      Verify.verify(trailers.get(DEBUG_INFO_TRAILER_KEY).equals(DEBUG_INFO));
    } catch (IllegalArgumentException e) {
      throw new VerifyException(e);
    }
    latch.countDown();
  }
}, new Metadata());

call.sendMessage(HelloRequest.newBuilder().build());
call.halfClose();

if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) {
  throw new RuntimeException("timeout!");
}
}
7.异常信息抽取

io.grpc.Status提供了异常读取工具方法。

staticvoidverifyErrorReply(Throwable t) {
    // @1 解析Server异常信息
    Status status = Status.fromThrowable(t);
    // @2 获取Server自定义异常元数据信息
    Metadata trailers = Status.trailersFromThrowable(t);
    Verify.verify(status.getCode() == Status.Code.INTERNAL);
    Verify.verify(trailers.containsKey(DEBUG_INFO_TRAILER_KEY));
    Verify.verify(status.getDescription().equals(DEBUG_DESC));
    try {
        Verify.verify(trailers.get(DEBUG_INFO_TRAILER_KEY)
        .equals(DEBUG_INFO));
    } catch (IllegalArgumentException e) {
      throw new VerifyException(e);
    }
}

小结:Client对Server返回异常信息的处理,除了返回Server异常状态码和描述信息外,还可以通过Metadata自定义更多的异常信息。Client提供了5种调用方式解析Server异常错误信息,同时也提供了很好的使用示例范本。

三、示例代码
https://github.com/grpc/grpc-java/tree/master/examples/src/main/java/io/grpc/examples