我个人对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. Python技法:实用运维脚本编写(进程/文件/目录操作)

    Python在很大程度上可以对shell脚本进行替代.笔者一般单行命令用shell,复杂点的多行操作就直接用Python了.这篇文章就归纳一下Python的一些实用脚本操作. 1. 执行外部程序或命令 ...

  2. 基础路径规划算法(Dijikstra、A*、D*)总结

    引言 在一张固定地图上选择一条路径,当存在多条可选的路径之时,需要选择代价最小的那条路径.我们称这类问题为最短路径的选择问题.解决这个问题最经典的算法为Dijikstra算法,其通过贪心选择的步骤从源 ...

  3. SRIO RapidIO (SRIO)协议介绍(-)

    1     导读 1.1    与PCIe的差异 典型的PCIe结构定义了一个以单个中央处理器为核心的计算机系统,比如我们常见的工控机.PXIe机箱控制器.服务器内的IO设备.从系统架构来看,这个结构 ...

  4. leetcode 524. Longest Word in Dictionary through Deleting 通过删除字母匹配到字典里最长单词

    一.题目大意 https://leetcode.cn/problems/longest-word-in-dictionary-through-deleting 给你一个字符串 s 和一个字符串数组 d ...

  5. 图解Dijkstra(迪杰斯特拉)算法+代码实现

    简介 Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径.主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止.Dijkstra算法是很有代表性的 ...

  6. django请求生命周期流程与路由层相关知识

    目录 请求生命周期流程图 路由层之路由匹配 无名有名分组 反向解析 无名有名分组反向解析 路由分发 名称空间 请求生命周期流程图 django请求生命周期流程图 路由层之路由匹配 我们都知道,路由层是 ...

  7. Es图形化软件使用之ElasticSearch-head、Kibana,Elasticsearch之-倒排索引操作、映射管理、文档增删改查

    今日内容概要 ElasticSearch之-ElasticSearch-head ElasticSearch之-安装Kibana Elasticsearch之-倒排索引 Elasticsearch之- ...

  8. 『忘了再学』Shell基础 — 21、变量的测试与内容置换

    目录 1.什么是变量的测试与内容置换 2.变量的测试与内容置换 3.示例 例1: 例2: 例3: 1.什么是变量的测试与内容置换 我们之前说过,在Shell中,一个变量未定义,和一个变量为空值的输出效 ...

  9. SSH 的使用和配置

    命令 ssh user@hostname -p port Windows 下首次执行这个命令会由于 Windows 默认没有运行 ssh-agent 导致无法连接,可以通过在 powershell 下 ...

  10. MySQL、SqlServer、Oracle,这三种数据库的优缺点,你知道吗?

    盘点MySQL.SqlServer.Oracle 三种数据库优缺点 MySQL SqlServer Oracle 一.MySQL 优 点 体积小.速度快.总体拥有成本低,开源:支持多种操作系统:是开源 ...