我個(gè)人對GRPC是比較感興趣的,,最近在玩通過前端調(diào)用GRPC,。通過前端調(diào)用GRPC業(yè)界有兩種方式:GRPC Web和GRPC JSON轉(zhuǎn)碼,。 GRPC Web 通過JS或者Blazor WASM調(diào)用GRPC,微軟在這方面做的還是很好的,從.NET Core3.0之后就提供了兩種實(shí)現(xiàn)GRPC Web的方式(Grpc.AspNetCore.Web與Envoy)。我在之前的一篇里也寫過如何通過Blazor WASM調(diào)用GRPC Web。 GRPC JSON 通過Restful api調(diào)用一個(gè)代理服務(wù),,代理服務(wù)將數(shù)據(jù)轉(zhuǎn)發(fā)到GRPC Server就是GRPC JSON。微軟從.NET7開始也正式提供了GRPC JSON轉(zhuǎn)碼的方式,。 為什么要造輪子既然有了GRPC Web與GRPC Json,,那我為啥還要再造這么一個(gè)輪子? 原因是有位同行看了如何通過Blazor WASM調(diào)用GRPC Web 這篇文章后,,告訴我微信小程序目前沒辦法通過這種方式調(diào)用GRPC,。我當(dāng)時(shí)覺得很奇怪,微信小程序也屬于前端,,為啥不能調(diào)用GRPC呢,? GRPC Web+小程序遇到的問題只是聽說還不能確認(rèn),要自己試一試,,于是我用GRPC Web的方式讓小程序調(diào)用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\ 然后將生成的代碼引入小程序端,發(fā)現(xiàn)確實(shí)有問題,,微信小程序編譯后無法正常識別GRPC的namespace,,會(huì)報(bào)以下錯(cuò)誤:
去查了下原因,應(yīng)該是因?yàn)?em>小程序目前不支持protobuf序列化,。然后我通過一種取巧的方式手動(dòng)在生成的GRPC JS中添加了proto變量 var proto = {} 再次嘗試,,雖然proto能找到,但是又找不到其他對象,,并且最主要的是GRPC JS Client是通過proto工具生成的,,每次生成手動(dòng)定義proto變量也不現(xiàn)實(shí)。 GRPC Web+小程序遇到問題總結(jié):
既然小程序通過GRPC Web方式調(diào)用GRPC失敗,,那還有GRPC Json,。 GRPC JSON+Envoy+小程序遇到的問題我使用了Envoy來充當(dāng)restful代理,調(diào)用GRPC,。我在之前有一篇通過Envoy JSON代理GRPC的帖子,。按這個(gè)帖子來了一遍。 計(jì)劃通過docker-compose方式運(yùn)行GRPC Server和Envoy代理,。 既然用GRPC,那肯定用http2/http2,,在docker里運(yùn)行.net core必然需要證書,,沒有證書就自己搞一個(gè)自簽證書。
證書有了,,在GRPC里配置https
然后就開始配置envoy 首先生成grpc proto描述符
然后定義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,,主要是信任自簽證書 COPY ['server.crt','/usr/local/share/ca-certificates/'] RUN ['update-ca-certificates'] 最后就是定義docker-compsoe.yaml
最后通過docker-compsoe up -d運(yùn)行,,但是postman調(diào)用的時(shí)候,envoy與grpcserver的通信連接成功了,,但是數(shù)據(jù)傳輸時(shí)總是被 connection reset,,去github上找原因也沒找到。至此grpc json+envoy又失敗了,。 GRPC JSON+Envoy+小程序遇到問題總結(jié):
既然envoy走不通不行,,那就自己造一個(gè)吧。 開始造輪子GRPC JSON的形式,,原理就是通過一個(gè)web api接收restful請求,,將請求數(shù)據(jù)轉(zhuǎn)發(fā)到GRPC Server。 首先創(chuàng)建一個(gè)web api命名為GrpcGateway,,并引入proto文件,,生成grpc client代碼
然后創(chuàng)建一個(gè)控制器去接受restful請求,而grpc client可采用反射來創(chuàng)建,。 [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> /// 調(diào)用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('參數(shù)不能為空'); } 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('參數(shù)不能為空'); } 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()); }
} } 然后注入動(dòng)態(tài)反射創(chuàng)建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:///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 baseWORKDIR /app EXPOSE 16666FROM 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
通過docker-compsoe up -d 啟動(dòng) 通過postman調(diào)用,看到200狀態(tài)碼,,終于成功了,,最后試了下小程序也能通過這種方式調(diào)用后端GRPC了,整個(gè)人都舒服了... 關(guān)注我獲取技術(shù)分享 |
|