我个人对GRPC是比较感兴趣的,最近在玩通过前端调用GRPC。通过前端调用GRPC业界有两种方式:GRPC Web和GRPC JSON转码。

GRPC Web

通过JS或者Blazor WASM调用GRPC,微软在这方面做的还是很好的,从.NET Core3.0之后就提供了两种实现GRPC Web的方式(Grpc.AspNetCore.Web与Envoy)。我在之前的一篇里也写过如何通过Blazor WASM调用GRPC Web。

GRPC JSON

通过Restful api调用一个代理服务,代理服务将数据转发到GRPC Server就是GRPC JSON。微软从.NET7开始也正式提供了GRPC JSON转码的方式。

为什么要造轮子

既然有了GRPC Web与GRPC Json,那我为啥还要再造这么一个轮子?

原因是有位同行看了如何通过Blazor WASM调用GRPC Web 这篇文章后,告诉我微信小程序目前没办法通过这种方式调用GRPC。我当时觉得很奇怪,微信小程序也属于前端,为啥不能调用GRPC呢?

GRPC Web+小程序遇到的问题

只是听说还不能确认,要自己试一试,于是我用GRPC Web的方式让小程序调用GRPC,首先需要生成GRPC JS Client代码:

protoc.exe -I=. test.proto --js_out=import_style=commonjs:.\grpcjs\ --plugin=protoc-gen-grpc=.\protoc-gen-grpc-web.exe --grpc-web_out=import_style=commonjs,mode=grpcwebtext:.\grpcjs\

然后将生成的代码引入小程序端,发现确实有问题,微信小程序编译后无法正常识别GRPC的namespace,会报以下错误:

proto is not defined

去查了下原因,应该是因为小程序目前不支持protobuf序列化。然后我通过一种取巧的方式手动在生成的GRPC JS中添加了proto变量

var proto = {}

再次尝试,虽然proto能找到,但是又找不到其他对象,并且最主要的是GRPC JS Client是通过proto工具生成的,每次生成手动定义proto变量也不现实。

GRPC Web+小程序遇到问题总结:

  1. 小程序目前不支持protobuf序列化
  2. 手动修改生成的GRPC JS Client不友好

既然小程序通过GRPC Web方式调用GRPC失败,那还有GRPC Json。

GRPC JSON+Envoy+小程序遇到的问题

我使用了Envoy来充当restful代理,调用GRPC。我在之前有一篇通过Envoy JSON代理GRPC的帖子。按这个帖子来了一遍。

计划通过docker-compose方式运行GRPC Server和Envoy代理。

既然用GRPC,那肯定用http2/http2,在docker里运行.net core必然需要证书,没有证书就自己搞一个自签证书。

openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.cer
openssl pkcs12 -export -in server.cer -inkey server.key -out server.pfx

证书有了,在GRPC里配置https

    builder.WebHost.ConfigureKestrel(o =>
{ o.ListenAnyIP(1111, p =>
{
p.Protocols = HttpProtocols.Http2;
p.UseHttps("/app/server.pfx", "123456");
});
});

然后就开始配置envoy

首先生成grpc proto描述符

protoc.exe -I=.  --descriptor_set_out=.\test.pb --include_imports .\test.proto  --proto_path=.

然后定义envoy配置文件

admin:
address:
socket_address: {address: 0.0.0.0, port_value: 9901} static_resources:
listeners:
- name: listener1
address:
socket_address: {address: 0.0.0.0, port_value: 10000}
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: grpc_json
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: {prefix: "/test"}
route:
cluster: grpc
http_filters:
- name: envoy.filters.http.grpc_json_transcoder
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder
proto_descriptor: "/etc/envoy/test.pb"
services: ["test"]
print_options:
add_whitespace: true
always_print_primitive_fields: true
always_print_enums_as_ints: false
preserve_proto_field_names: false
auto_mapping: true
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: grpc
type: static
connect_timeout: 15s
lb_policy: ROUND_ROBIN
dns_lookup_family: V4_ONLY
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options: {}
load_assignment:
cluster_name: grpc
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 某ip
port_value: 1111

下面就定义envoy的dockerfile,主要是信任自签证书

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM envoyproxy/envoy-dev:e834c24e061b710348ffd72016d5d1069698b4ff

COPY ["server.crt","/usr/local/share/ca-certificates/"]

RUN ["update-ca-certificates"]

最后就是定义docker-compsoe.yaml

version: '3.4'

services:
myenvoy:
image: myenvoy
container_name: myenvoy
command: "-c /etc/envoy/envoy.yaml --log-level debug"
build:
context: .
dockerfile: GrpcServer/DockerfileEnvoy
volumes:
- "grpcpbs/:/etc/envoy/"
- "grpcpbs/logs:/logs"
ports:
- "9901:9901"
- "10000:10000"
depends_on:
- grpcserver
networks:
- mynetwork
grpcserver:
image: grpcserver
container_name: grpcserver
networks:
- mynetwork
build:
context: .
dockerfile: GrpcServer/Dockerfile
ports:
- "1111:1111" networks:
mynetwork:

最后通过docker-compsoe up -d运行,但是postman调用的时候,envoy与grpcserver的通信连接成功了,但是数据传输时总是被 connection reset,去github上找原因也没找到。至此grpc json+envoy又失败了。

GRPC JSON+Envoy+小程序遇到问题总结:

  1. 数据传输时connection 被莫名reset

既然envoy走不通不行,那就自己造一个吧。

开始造轮子

GRPC JSON的形式,原理就是通过一个web api接收restful请求,将请求数据转发到GRPC Server。

首先创建一个web api命名为GrpcGateway,并引入proto文件,生成grpc client代码

  <ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.20.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.46.0" />
<PackageReference Include="Grpc.Tools" Version="2.46.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="..\*.proto" GrpcServices="Client" />
</ItemGroup>

然后创建一个控制器去接受restful请求,而grpc client可采用反射来创建。

 [ApiController]
[Route("[controller]")]
public class ProcessGrpcRequestController : ControllerBase
{
private readonly ILogger<ProcessGrpcRequestController> _logger;
private readonly Func<string, ClientBase> _getGrpcClient;
public ProcessGrpcRequestController(ILogger<ProcessGrpcRequestController> logger, Func<string, ClientBase> getGrpcClient)
{
_logger = logger;
_getGrpcClient = getGrpcClient;
} /// <summary>
/// 调用grpc
/// </summary>
/// <param name="serviceName">Grpc Service Name 从proto文件中查询</param>
/// <param name="method">Grpc Method Name 从proto文件中查询</param>
/// <returns></returns>
[HttpPost("serviceName/{serviceName}/method/{method}")]
public async Task<IActionResult> ProcessAsync(string serviceName, string method)
{
try
{
if (string.IsNullOrEmpty(serviceName))
{
return BadRequest("serviceName不能为空");
}
if (string.IsNullOrEmpty(method))
{
return BadRequest("method不能为空");
} using var sr = new StreamReader(Request.Body, leaveOpen: true, encoding: Encoding.UTF8);
var paramJson = await sr.ReadToEndAsync();
if (string.IsNullOrEmpty(paramJson))
{
return BadRequest("参数不能为空");
}
var client = _getGrpcClient(serviceName);
if (client == null)
{
return NotFound();
} Type t = client.GetType();
var processMethod = t.GetMethods().Where(e => e.Name == method).FirstOrDefault();
if (processMethod == null)
{
return NotFound();
} var parameters = processMethod.GetParameters();
if (parameters == null)
{
return NotFound();
} var param = JsonConvert.DeserializeObject(paramJson, parameters[0].ParameterType);
if (param == null)
{
return BadRequest("参数不能为空");
}
var pt = param.GetType();
var headers = new Metadata(); if (Request.Headers.Keys.Contains("Authorization"))
{
headers.Add("Authorization", Request.Headers["Authorization"]);
} var result = processMethod.Invoke(client, new object[] { param, headers, null, null }); return Ok(result);
}
catch(Exception ex) when (
ex.InnerException !=null && ex.InnerException !=null && ex.InnerException is RpcException &&
((ex.InnerException as RpcException).StatusCode == Grpc.Core.StatusCode.Unauthenticated ||
((ex.InnerException as RpcException).StatusCode == Grpc.Core.StatusCode.PermissionDenied)))
{
_logger.LogError(ex, ex.ToString());
return Unauthorized();
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return BadRequest(ex.ToString());
} }
}

然后注入动态反射创建grpc client的方法

            services.AddScoped(p => {
Func<string, ClientBase> func = serviceName =>
{
var channel = GrpcChannel.ForAddress(grpcServerAddress); var parentClassName = $"{serviceName}"; var assembly = Assembly.Load("你的dll名字");
var parentType = assembly.GetType(parentClassName); var clientType= parentType.GetNestedType($"{serviceName}Client");
if (clientType == null)
{
throw new Exception($"serviceName:{serviceName}不存在");
}
var client = Activator.CreateInstance(clientType, new object[] { channel });
return (ClientBase)client;
};
return func;
});

然后定义grpc gateway dockerfile ,最主要需要信任证书

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 16666 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["MyGateway/MyGateway.csproj", "MyGateway/"]
COPY . .
WORKDIR "/src/MyGateway" FROM build AS publish
RUN dotnet publish "MyGateway.csproj" -c Release -o /app/publish FROM base AS final
COPY ["server.crt","/usr/local/share/ca-certificates/"]
RUN ["update-ca-certificates"]
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyGateway.dll"]

最后通过定义docker-compose

version: '3.4'

services:
mygateway:
image: mygateway
container_name: mygateway
networks:
- mynetwork
build:
context: .
dockerfile: MyGateway/Dockerfile
ports:
- "2222:2222"
grpcserver:
image: grpcserver
container_name: grpcserver
networks:
- mynetwork
build:
context: .
dockerfile: GrpcServer/Dockerfile
ports:
- "1111:1111" networks:
mynetwork:

通过docker-compsoe up -d 启动

通过postman调用,看到200状态码,终于成功了,最后试了下小程序也能通过这种方式调用后端GRPC了,整个人都舒服了...

我又造了个轮子:GrpcGateway的更多相关文章

  1. 用Vue自己造个组件轮子,以及实践背后带来的思考

    前言 首先,向大家说声抱歉.由于之前的井底之蛙,误认为Vue.js还远没有覆盖到二三线城市的互联网小厂里.现在我错了,从我司的前端技术选型之路便可见端倪.以太原为例,已经有不少公司陆续开始采用Vue. ...

  2. 一步一步造个IoC轮子(二),详解泛型工厂

    一步一步造个Ioc轮子目录 一步一步造个IoC轮子(一):Ioc是什么 一步一步造个IoC轮子(二):详解泛型工厂 一步一步造个IoC轮子(三):构造基本的IoC容器 详解泛型工厂 既然我说IoC容器 ...

  3. 一步一步造个IoC轮子(一):IoC是什么

    一步一步造个Ioc轮子目录 一步一步造个IoC轮子(一):IoC是什么 一步一步造个IoC轮子(二):详解泛型工厂 一步一步造个IoC轮子(三):构造基本的IoC容器 前言 .net core正式版前 ...

  4. 一步一步造个IoC轮子(三):构造基本的IoC容器

    一步一步造个Ioc轮子目录 一步一步造个IoC轮子(一):Ioc是什么 一步一步造个IoC轮子(二):详解泛型工厂 一步一步造个IoC轮子(三):构造基本的IoC容器 定义容器 首先,我们来画个大饼, ...

  5. python类(4)——自己造第一个轮子

    先做简单版本,再一步步增加功能 1.简单目的:要实现这样一个功能,能够连接服务器,登录账号,查询账号委托信息,如果有委托信息,撤销委托. 属性(不同账户之间差别):账户,密码 方法(不同账户之间都要用 ...

  6. 关于Mvc的分页写法

    关于asp.net mvc的分页,网上已经有很多了.本来也想借用,先看了杨涛写的分页控件,感觉用起来稍微有点复杂,而我只需要简单的分页.分页我写过很多次,原理也熟悉,就是构造首页.上一页.下一页及末页 ...

  7. 我造了个好大的"轮子",居然还不是"圆"的!

      我造的这个"轮子"指的是集低代码开发与运维为一体的平台,为什么说它不是"圆"的,因为它有些与众不同,甚至可以说是有些另类.至于为什么造这个"轮子& ...

  8. 用Go造轮子-管理集群中的配置文件

    写在前面 最近一年来,我都在做公司的RTB广告系统,包括SSP曝光服务,ADX服务和DSP系统.因为是第一次在公司用Go语言实现这么一个大的系统,中间因为各种原因造了很多轮子.现在稍微有点时间,觉着有 ...

  9. 【原创】重复造轮子之高仿EntityFramework

    前言 在上一篇<[原创]打造基于Dapper的数据访问层>中,Dapper在应付多表自由关联.分组查询.匿名查询等应用场景时经常要手动写SQL语句.看着代码里满屏的红色SQL字符串,简直头 ...

随机推荐

  1. 使用VPLS跨地域多站点建立二层交换网络

    VLL解决方案 https://www.cnblogs.com/darkchen/p/14652540.html 接上篇文档引出VPLS VLL可以跨地域建立二层交换网络,但有个缺陷是只支持两个sit ...

  2. spring boot redis 写入异常

    redis 的 key value 使用 json 序列化.反序列化时,写入的 bean 不能是 final 类型的类,否则无法解析

  3. 使用 AgileConfig 动态配置 NLog

    NLog 是我们在 .NET 领域使用非常广泛的日志组件.它默认使用 xml 来维护它的配置.最近有几个同学问我当使用 AgileConfig 的时候如何配置 NLog .因为 AgileConfig ...

  4. python常用标准库(时间模块 time和datetime)

    常用的标准库 time时间模块 import time time -- 获取本地时间戳 时间戳又被称之为是Unix时间戳,原本是在Unix系统中的计时工具. 它的含义是从1970年1月1日(UTC/G ...

  5. 一些实验中用过的python函数/方法(持续更新)

    衡量运行时间 很多时候你需要计算某段代码执行所需的时间,可以使用 time 模块来实现这个功能. import time startTime = time.time() # write your co ...

  6. 【SpringSecurity系列1】基于SpringSecurity实现前后端分离无状态Rest API的权限控制

    源码传送门: https://github.com/ningzuoxin/zxning-springsecurity-demos/tree/master/01-springsecurity-state ...

  7. 2.Tensor Shape《Pytorch神经网络高效入门教程》Deeplizard

            ,之后,我们张量和基础数据的形状酱油卷积运算来改变. 卷积改变了高度和宽度维度以及颜色通道的数量.

  8. ExtJS 布局-Absolute布局(Absolute layout)

    更新记录: 2022年5月31日 发布本篇 1.说明 使用xy配置项设置子组件在父容器中绝对位置,本质是将子组件的CSS的position设置为absolute,然后使用x和y配置项映射到CSS的to ...

  9. 教你用VS code 生成vue-cli代码片段

    可以自定义设置名字:name.json { "Print to console": { "prefix": "vue", "bod ...

  10. ABAP CDS - Language Elements

    The following sections summarize the language elements of the DDL and DCL of the ABAP CDS, arranged ...