All Articles

gRPC的几种请求响应模式

1. 前言

gRPC除了1:1的请求应答模式之外,还支持流式的请求响应模式,而在JS语法下的使用语句也有点意思,这里做一个简单整理。

首先给出使用的proto定义文件,下文所有的代码都以这个定义为准:

2. 单请求 单响应

2.1 说明

请求模式:单请求,单响应

服务端:

客户端:

  • 客户端比较简单,自行构造请求,然后使用回调得到返回值即可。

2.2 例子

Proto定义的请求:

rpc GetBook (GetBookRequest) returns (Book) {}

Server:

const getBook = (call: grpc.ServerUnaryCall<GetBookRequest>, callback: grpc.sendUnaryData<Book>) => {
  const book = new Book();

  book.setTitle("DefaultBook");
  book.setAuthor("DefaultAuthor");

  log(`[getBook] Done: ${JSON.stringify(book.toObject())}`);
  callback(null, book);
};

client:

const getBook = async (isbn: number) => {
  return new Promise((resolve, reject) => {
    const request = new GetBookRequest();
    request.setIsbn(isbn);

    log(`[getBook] Request: ${JSON.stringify(request.toObject())}`);

    client.getBook(request, (err, book: Book) => {
      if (err != null) {
        debug(`[getBook] err:\nerr.message: ${err.message}\nerr.stack:\n${err.stack}`);
        reject(err); return;
      }
      log(`[getBook] Book: ${JSON.stringify(book.toObject())}`);
      resolve(book);
    });
  });
};

3. 单请求 流响应

3.1 说明

请求模式:单请求,流式响应(多响应)

服务端:

  • 收到的请求是一个grpc.ServerWriteableStream<RequestType>类型,和单请求单响应的情况不同,作为可写入流该对象还要负责返回数据,但作为请求对象处理的时候,主要职责仍旧是承载request: RequestType
  • 返回响应需要使用请求给到的这个可写入流来write返回ResponseType,全部结束之后需要主动关闭可写入流,即end

客户端:

  • 请求仍旧是自行构造即可。
  • 响应是一个grpc.ClientReadableStream<ResponseType>类型,是一个可读取流,客户端需要监听data事件,得到返回的ResponseType数据,并监听end事件,标识响应流的结束。

3.2 范例

Proto定义的请求:

rpc GetBooksViaAuthor (GetBookViaAuthor) returns (stream Book) {}

Server:

const getBooksViaAuthor = (call: grpc.ServerWriteableStream<GetBookViaAuthor>) => {
  const request = call.request as GetBookViaAuthor;

  log(`[getBooksViaAuthor] Request: ${JSON.stringify(request.toObject())}`);
  for (let i = 1; i <= 10; i++) {
    const reply = new Book();
    reply.setTitle(`Book${i}`);
    reply.setAuthor(request.getAuthor());
    reply.setIsbn(i);
    log(`[getBooksViaAuthor] Write: ${JSON.stringify(reply.toObject())}`);
    call.write(reply);
  }
  log("[getBooksViaAuthor] Done.");
  call.end();
};

Client:

const getBooksViaAuthor = (author: string) => {
  return new Promise((resolve) => {
    const request = new GetBookViaAuthor();
    request.setAuthor(author);

    log(`[getBooksViaAuthor] Request: ${JSON.stringify(request.toObject())}`);

    const stream: grpc.ClientReadableStream<GetBookViaAuthor> = client.getBooksViaAuthor(request);
    stream.on("data", (data: Book) => {
      log(`[getBooksViaAuthor] Book: ${JSON.stringify(data.toObject())}`);
    });
    stream.on("end", () => {
      log("[getBooksViaAuthor] Done.");
      resolve();
    });
  });
};

4. 流请求 单响应

4.1 说明

请求模式:流式请求(多请求),单响应

服务端:

  • 收到的请求是一个grpc.ServerReadableStream<RequestType>类型,是一个可读取流,需要监听data事件获得RequestType,并监听end事件得到所有请求获得结束的通知,然后开始处理服务端业务。
  • 返回的响应仍旧是一个grpc.sendUnaryData<ResponseType>,回调函数,用于处理结束后返回结果。

客户端:

  • 和上面几个模式稍稍有点不同,客户端直接发起请求,得到一个可写入流的返回值grpc.ClientWritableStream<RequestType>,并得到一个回调函数。
  • 在可写入流里使用write方法,向服务端发出RequestType的请求,并使用end方法通知服务端请求发送结束。
  • 在回调函数里处理服务端返回回来的ResponseType结果。

4.2 范例

Proto定义的请求:

rpc GetGreatestBook (stream GetBookRequest) returns (Book) {}

Server:

const getGreatestBook = (call: grpc.ServerReadableStream<GetBookRequest>, callback: grpc.sendUnaryData<Book>) => {
  let lastOne: GetBookRequest;
  call.on("data", (request: GetBookRequest) => {
    log(`[getGreatestBook] Request: ${JSON.stringify(request.toObject())}`);
    lastOne = request;
  });
  call.on("end", () => {
    const reply = new Book();
    reply.setIsbn(lastOne.getIsbn());
    reply.setTitle("LastOne");
    reply.setAuthor("LastOne");
    log(`[getGreatestBook] Done: ${JSON.stringify(reply.toObject())}`);
    callback(null, reply);
  });
};

Client:

const getGreatestBook = () => {
  return new Promise((resolve) => {
    const stream: grpc.ClientWritableStream<GetBookRequest> = client.getGreatestBook((err, data: Book) => {
      if (err != null) {
        log(`[getGreatestBook] err:\nerr.message: ${err.message}\nerr.stack:\n${err.stack}`);
      }
      log(`[getGreatestBook] Book: ${JSON.stringify(data.toObject())}`);
      resolve();
    });

    for (let i = 0; i < 10; i++) {
      const req = new GetBookRequest();
      req.setIsbn(i);
      log(`[getGreatestBook] Request: ${JSON.stringify(req.toObject())}`);
      stream.write(req);
    }
    stream.end();
  });
};

5. 流请求 流响应

5.1 说明

请求模式:流式请求(多请求),流式响应(多响应),实质上的请求和响应还是一对一关系

服务端:

  • 得到的参数只有一个,是一个grpc.ServerDuplexStream<RequestType, ResponseType>的双向流。
  • 需要程序员监听data事件,获取RequestType的请求,并使用write方法,返回ResponseType的结果,这里基本上是1:1的应答关系。
  • 然后在监听到end事件并处理完服务端逻辑之后,使用end方法,告知客户端处理完成。

客户端:

5.2 范例

Proto定义的请求:

rpc GetBooks (stream GetBookRequest) returns (stream Book) {}

Server:

const getBooks = (call: grpc.ServerDuplexStream<GetBookRequest, Book>) => {
  call.on("data", (request: GetBookRequest) => {
    const reply = new Book();
    reply.setTitle(`Book${request.getIsbn()}`);
    reply.setAuthor(`Author${request.getIsbn()}`);
    reply.setIsbn(request.getIsbn());
    log(`[getBooks] Write: ${JSON.stringify(reply.toObject())}`);
    call.write(reply);
  });
  call.on("end", () => {
    log("[getBooks] Done.");
    call.end();
  });
};

Client:

const getBooks = () => {
  return new Promise((resolve) => {
    const stream: grpc.ClientDuplexStream<GetBookRequest, Book> = client.getBooks();

    stream.on("data", (data: Book) => {
      log(`[getBooks] Book: ${JSON.stringify(data.toObject())}`);
    });
    stream.on("end", () => {
      log("[getBooks] Done.");
      resolve();
    });

    for (let i = 0; i < 10; i++) {
      const req = new GetBookRequest();
      req.setIsbn(i);
      log(`[getBooks] Request: ${JSON.stringify(req.toObject())}`);
      stream.write(req);
    }
    stream.end();
  });
};

6. 完整可运行的范例

完整的代码请参见:v2.2.2/examples/src

EOF

Published 2018/2/11

Some tech & personal blog posts