gRPC-Web 踩坑记

     从张队长的公众号得知 gRPC-Web 发布了,出于对 gRPC 的喜爱,决定周末踩踩坑。
     从 https://github.com/grpc/grpc-dotnet 克隆了代码下来,examples/Browser 这个项目就是 gRPC-Web 的例子。打开 Browser.sln 看一下目录结构。
     整个解决方案只有 Server 这一个项目。

gRPC-Web 踩坑记_第1张图片


     先看 Protos 文件夹,里面只有一个 greet.proto ,熟悉 gRPC 的都知道,是 gRPC 的接口定义。

gRPC-Web 踩坑记_第2张图片

     文件中定义了 HelloRequest、HelloReply 消息体,Greeter 服务,及 Greeter服务的两个方法 SayHello SayHellos

我们再打开 Server.csproj 项目文件。

gRPC-Web 踩坑记_第3张图片

     从图中这句能看出,这个 proto 文件是从父级路径 Link 过来的;而且作为GrpcServices 设为了服务器模式。有了这个设置,且引用了 Grpc.AspNetCore,那么在生成的时候就会生成对应的类和接口。
     我们来看 GreeterService.cs 文件,GreeterService 这个类是 gRPC 接口的实现。

gRPC-Web 踩坑记_第4张图片

     SayHello 方法简单地在传入参数前面加了个 "Hello "返回了。SayHellos 是个返回服务端流的方法,gRPC-Web 支持服务端流,不支持客户端流和双向流。这个方法也简单的返回了传入参数,前边加了 "Hello " 后面加了序号。

     我们再来看 wwwroot/Scripts 文件夹,里面有 greet_grpc_web_pb.js、greet_pb.js、index.js 三个JS文件。根据官方文档能够得知,greet_grpc_web_pb.js、greet_pb.js 是使用工具根据 greet.proto 生成的 js 包,就像生成的C#类一样。
index.js 是需要手动编写的。

gRPC-Web 踩坑记_第5张图片

     看格式我们就知道,这个包是需要编译的。(require 只有 nodejs 才支持)  index.js 首先引用了 工具生成的 js 包,然后实例化了一个 GreeterClient 服务。然后绑定了 sendInput 这个按钮的点击事件,

sendInput.onclick = function () {
    var request = new HelloRequest();
    request.setName(nameInput.value);

    client.sayHello(request, {}, (err, response) => {
        resultText.innerHTML = htmlEscape(response.getMessage());
    });
};

     在事件中,先实例化了 HelloRequest 对象 request,设置 name 值,然后调用了服务的 sayHello 方法, request 作为入参,在回调方法里把出参放到 resultText 文本框里。

     那么这个 index.js 是什么时候编译的呢?我们看回 Server.csproj 项目文件。

gRPC-Web 踩坑记_第6张图片

     这两个扩展编译项,其实功能是差不多的,一个是在“生成(Build)”之前执行,一个是在计算发布文件后执行。功能都是使用 npm 去编译 index.js。编译后会在 wwwroot 文件夹生成 dist/main.js 文件。这个文件是可以在 html 里直接引用、运行的。
     我们再来看 wwwroot/index.html 这里有简单的几个控件,还引用了编译好的 dist/main.js 文件。




    
    gRPC-Web ASP.NET Core example


    

gRPC-Web ASP.NET Core example

Hello:

     我们 F5 跑起来一下。

gRPC-Web 踩坑记_第7张图片

     现在流程我们差不多清楚了,那么动动手吧,改造一个 WebApi 项目,试一试是否好用。

     要改造的项目是公司的一个模板项目,有一些基本的框架,有一个读取数据库的列表页。今天改造的目的,是把这个列表页的获取列表数据的方式,由 WebApi 改为 gRPC.Web。
     先引用 Grpc 包,Grpc.AspNetCore  Grpc.AspNetCore.Web

gRPC-Web 踩坑记_第8张图片

     然后编写 proto 文件。我写了一个 common.proto 文件,代码如下:

syntax = "proto3";
option csharp_namespace = "你的项目的命名空间";

package CIGProtos;

// The greeting service definition.
service CommonRpc {
  // Sends a greeting
  rpc CallApi (RequestMessage) returns (ResponseMessage);
}

message RequestMessage {
  string ApiUrl = 1;
  string ApiParam = 2;
}

message ResponseMessage {
  string ApiResult = 1;
}

     这个 CommonRpc 的目的是代替 WebApi,所以格式上尽量能替换掉现有 WebApi。入参为 RequestMessage,一个 ApiUrl 用于替换现有 WebApi 的地址,ApiParam 替换现有 WebApi 的 Json 参数。出参为 ResponseMessage 是一个 Json 字符串。
CallApi 是调用服务的方法。
     然后我们来实现 gRPC 的接口,新建 CommonRpcService 类,继承 CommonRpc.CommonRpcBase。代码不放了,这个类的执行流程大概是,把服务层的所有类的所有方法,都反射出来,放到一个列表里,然后提供 CallApi 方法,根据参数里的 ApiUrl 属性对方法进行匹配,匹配到了,就从 serviceProvider 里找到实例,然后把参数里的 ApiParam 反序列化为方法参数的类型,进行调用;调用后把出参序列化后返回。

     然后我们打开 Startup.cs 在 ConfigureServices 节加上 services.AddGrpc();
在 Configure 节加上 app.UseGrpcWeb(); 及 endpoints.MapGrpcService().EnableGrpcWeb();

gRPC-Web 踩坑记_第9张图片

     这样我们服务端就已经写好了,下面是客户端。

     注意,对于使用 nodejs 作为前端的项目,不在本文讨论范围内,其实也只是比本文的方法少了一些步骤。
     我们首先需要根据 common.proto 生成 两个JS文件。这一步需要下载两个工具:
https://github.com/grpc/grpc-web/releases  我下载的是 protoc-gen-grpc-web-1.1.0-windows-x86_64.exe
https://github.com/protocolbuffers/protobuf/releases  注意这第2个,一大堆包,一不小心就下载错了。应该下载 最后面的 protoc-3.12.3-win64.zip 这样的 .zip 前是 操作系统的。

     下载后,放在同一个文件夹下,如果不放在同一个文件夹,需要设置 PATH 环境变量。把 common.proto 也放在些文件夹下,执行命令:
protoc common.proto --js_out=import_style=commonjs:.\ --grpc-web_out=import_style=commonjs,mode=grpcwebtext:.\
     然后就会生成两个 js 文件:common_pb.js  common_grpc_web_pb.js 。然后我们需要编写 index.js ,叫别的名也可。这一步骤对于非前端人员有一个难点,就是如何让前端 javascript 能调用到包里的方法,我就卡在这里两个小时,才查到可以使用把对象附加到 window 的方法。这个方法以前也用过,只不过没想到可以用在这里。 另外,要注意生成的两个 js 文件里的名称和大小写,比如,CommonRpc 要加一个 Client,RequestMessage 的设置ApiUrl 的设置器叫 setApiurl;建议要和生成的两个 js 对照一下。index.js 文件内容如下:

const { RequestMessage, ResponseMessage } = require('./common_pb.js');
const { CommonRpcClient } = require('./common_grpc_web_pb.js');

var CallApi = function (apiUrl, apiParam, callBack) {
    var client = new CommonRpcClient(window.location.origin);
    var request = new RequestMessage();
    request.setApiurl(apiUrl);
    request.setApiparam(apiParam);

    client.callApi(request, {}, (err, response) => {
        callBack(JSON.parse(response.getApiresult()));
    });
}
var model = {
    CallApi: CallApi
};
/**
 * @constructor
 * */
var mygrpc = function () {
    return model;
}
window.mygrpc = mygrpc;

     这个文件我也放在 wwwroot/Scripts 文件夹里。然后我们可以生成一下这个项目,让他执行 npm 的编译操作,当然也可以手动执行一下命令。在 wwwroot 文件夹执行  npm install 然后再执行 npx webpack scripts/index.js ,和生成效果是一样的。
     然后在页面里引用一下生成的 dist/main.js ,再来改页面上的代码,之前的代码为:

var that = this;
$.post("/控制器名/GetPageList",
   this.Query,
   function (data) {
      if (data.result) {
          that.tableData = data.listdata;
          that.pager.total = data.total;
       } else {
           that.$message.error("出错了:" + data.Message);
       }
   });

     页面框架用的 Vue。Query 是查询的信息:包含关键字、分页页数等;那么只需要改为:

var that = this;
new mygrpc().CallApi("/服务名/GetPageList",
   this.Query,
   function (data) {
      if (data.result) {
          that.tableData = data.listdata;
          that.pager.total = data.total;
       } else {
           that.$message.error("出错了:" + data.Message);
       }
   });

     不出意外,就已经能跑起来了。
     好了,还是很简单的吧。
     有几点想总结一下:
1,gRPC-Web 不是 gRPC ,没有利用 Http2。
2,gRPC 不能和 Controller 放在同一项目,但 gRPC-Web 可以。
3,gRPC-Web 提供了一种代替 WebApi 的选择,后台项目之间的调用推荐 gRPC 不推荐 gRPC-Web。

 

你可能感兴趣的:(AspNetCore,gRPC)