RSS

gRPC-Web 中的拦截器

我们很高兴地宣布,自 1.1.0 版本起,gRPC-web 开始支持拦截器。虽然当前的设计基于其他 gRPC 语言中可用的 gRPC 客户端拦截器,但它也包含了 gRPC-web 特有的功能,使拦截器能够轻松地与现代 Web 框架一起采用和使用。

简介

与其他 gRPC 语言类似,gRPC-web 支持一元和服务器流式拦截器。对于每种类型的拦截器,我们都定义了一个接口,其中包含单个 `intercept()` 方法

  • UnaryInterceptor
  • StreamInterceptor

`UnaryInterceptor` 接口的声明方式如下

/*
* @interface
*/
const UnaryInterceptor = function() {};

/**
 * @template REQUEST, RESPONSE
 * @param {!Request<REQUEST, RESPONSE>} request
 * @param {function(!Request<REQUEST,RESPONSE>):!Promise<!UnaryResponse<RESPONSE>>}
 *     invoker
 * @return {!Promise<!UnaryResponse<RESPONSE>>}
 */
UnaryInterceptor.prototype.intercept = function(request, invoker) {};

`intercept()` 方法接受两个参数

  • 类型为 grpc.web.Request 的 `request`
  • 一个 `invoker`,它在被调用时执行实际的 RPC

`StreamInterceptor` 接口声明类似,但 `invoker` 的返回类型是 `ClientReadableStream` 而不是 `Promise`。有关实现细节,请参阅 interceptor.js

我可以用拦截器做什么?

拦截器允许您执行以下操作:

  • 在传递原始 gRPC 请求之前更新它——例如,您可以注入额外的信息,例如身份验证头。
  • 操纵原始调用器函数的行为,例如绕过调用以便您可以使用缓存结果代替。
  • 在将响应返回给客户端之前更新它。

接下来您将看到一些示例。

一元拦截器示例

以下代码演示了一个执行以下操作的一元拦截器:

  • 它在 RPC 之前将一个字符串前置到 gRPC 请求消息中。
  • 它在收到 gRPC 响应消息后将其前置一个字符串。

这个简单的一元拦截器被定义为一个实现 `UnaryInterceptor` 接口的类

/**
 * @constructor
 * @implements {UnaryInterceptor}
 */
const SimpleUnaryInterceptor = function() {};

/** @override */
SimpleUnaryInterceptor.prototype.intercept = function(request, invoker) {
  // Update the request message before the RPC.
  const reqMsg = request.getRequestMessage();
  reqMsg.setMessage('[Intercept request]' + reqMsg.getMessage());

  // After the RPC returns successfully, update the response.
  return invoker(request).then((response) => {
    // You can also do something with response metadata here.
    console.log(response.getMetadata());

    // Update the response message.
    const responseMsg = response.getResponseMessage();
    responseMsg.setMessage('[Intercept response]' + responseMsg.getMessage());

    return response;
  });
};

流式拦截器示例

使用 `StreamInterceptor` 拦截来自 `ClientReadableStream` 的服务器流式响应需要更多注意。以下是需要遵循的主要步骤:

  1. 创建一个 `ClientReadableStream` 包装类,并使用它来拦截流事件,例如接收服务器响应。
  2. 创建一个实现 `StreamInterceptor` 并使用流包装器的类。

以下示例流包装器类拦截响应并在响应消息前添加一个字符串

/**
 * A ClientReadableStream wrapper.
 *
 * @template RESPONSE
 * @implements {ClientReadableStream}
 * @constructor
 * @param {!ClientReadableStream<RESPONSE>} stream
 */
const InterceptedStream = function(stream) {
  this.stream = stream;
};

/** @override */
InterceptedStream.prototype.on = function(eventType, callback) {
  if (eventType == 'data') {
    const newCallback = (response) => {
      // Update the response message.
      const msg = response.getMessage();
      response.setMessage('[Intercept response]' + msg);
      // Pass along the updated response.
      callback(response);
    };
    // Register the new callback.
    this.stream.on(eventType, newCallback);
  } else {
    // You can also override 'status', 'end', and 'error' eventTypes.
    this.stream.on(eventType, callback);
  }
  return this;
};

/** @override */
InterceptedStream.prototype.cancel = function() {
  this.stream.cancel();
  return this;
};

示例拦截器的 `intercept()` 方法返回一个包装流

/**
 * @constructor
 * @implements {StreamInterceptor}
 */
const TestStreamInterceptor = function() {};

/** @override */
TestStreamInterceptor.prototype.intercept = function(request, invoker) {
  return new InterceptedStream(invoker(request));
};

绑定拦截器

通过使用适当的选项键传递拦截器实例数组,您可以在客户端实例化时将拦截器绑定到客户端

const promiseClient = new MyServicePromiseClient(
    host, creds, {'unaryInterceptors': [interceptor1, interceptor2, interceptor3]});

const client = new MyServiceClient(
    host, creds, {'streamInterceptors': [interceptor1, interceptor2, interceptor3]});

反馈

发现 `grpc-web` 的问题或需要新功能?请在 grpc-web 仓库提交一个问题。如果您有一般性问题或意见,请考虑发布到 gRPC 邮件列表或发送电子邮件至 grpc-web-team@google.com