前言
簡單整理一下grpc。
正文
什麼是grpc?
- 一個遠端過程呼叫框架,可以像類一樣呼叫遠端方法。
這種模式一般來說就是代理模式,然後都是框架自我生成的。
- 由google 公司發起並開源,故而前面有個g。
grpc的特點:
-
提供幾乎所有主流語言的實現,打破語言隔閡。
-
基於http/2,開放協議,收到廣泛的支援,易於實現和整合
http/2 有個特點哈,就是更快,可以瞭解下,後續會專門整理關於http相關的,要等網路原理整理完後。
-
預設使用protocol buffers 序列化,效能相較於restful json好很多
-
工具鏈成熟,程式碼生成便捷,開箱即用
-
支援雙向流式的請求和相應,對批量處理、低延遲場景友好
.net 生態對gRPC的支援情況:
-
提供基於httpClient 的原生框架的實現
-
提供了原生的ASP .net Core 整合庫
-
提供完整的程式碼工具
-
visual studio 和 visual Stuido Code 提供proto 檔案的智慧提示
實踐:
service 服務端:
.net core 在服務端使用的包:
- Grpc.AspNetCore
客戶端使用的包:
-
google.protobuf
-
Grpc.Net.Client
-
Grpc.Net.ClientFactory
-
Grpc.Tools
通過工具會生成proto 檔案。
proto 檔案:
-
定義包、庫名
-
定義服務"service"
-
定義輸入輸出模型"message"
gRPC 異常處理:
-
使用Grpc.Core.RpcException
-
使用Grpc.Core.Interceptors.Interceptor
grpc 的加密方式:
grpc 因為是http2,預設使用https證書。
也可以不使用證書,裡面可以設定,如果設定了,那麼就是不加密的。
- 注入服務
services.AddGrpcClient<Greeter.GreeterClient>(options =>
{
options.Address = new Uri("https://localhost:5001");
});
- 測試程式碼
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
Greeter.GreeterClient service = context.RequestServices.GetService<Greeter.GreeterClient>();
HelloRequest helloRequest = new HelloRequest();
var reply = await service.SayHelloAsync(helloRequest);
Console.Read();
});
endpoints.MapControllers();
});
- 結果
這樣就可以呼叫了。
這裡如果我們使用http://localhost:5000,那麼訪問會失敗,顯示協議不可用,不支援http1.1。
那麼如果需要http的話,就需要配置一些http2了。
在服務端的配置檔案中,這樣寫:
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://*:5000"
},
"Https": {
"Url": "https://*:5001"
},
"Http2": {
"Url": "http://*:5002",
"Protocols": "Http2"
}
}
}
將http2的url設定為http://*:5002。
然後你就發現報錯了:
這個問題也是比較簡單的,因為http2預設使用加密,也就是應用解密的方式去解,自然就出錯了。
那麼需要這樣:
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport",true);
支援不使用加密方式的http2。如果是在內網內,可以不使用加密方式,這樣更加流暢。
那麼還有一種情況就是如果在有證書的情況下,那麼我們的開發環境可能就跑不過了,因為本地使用自簽名證書,通過不了驗證。
下面是生成證書的powershell指令碼,百度的。
# setup certificate properties including the commonName (DNSName) property for Chrome 58+
$certificate = New-SelfSignedCertificate `
-Subject 改成自己想要的標題不要帶亂七八糟的符號(安裝證書的時候會顯示這個) `
-DnsName 友好域名 `
-KeyAlgorithm RSA `
-KeyLength 2048 `
-NotBefore (Get-Date) `
-NotAfter (Get-Date).AddYears(2) `
-CertStoreLocation "cert:CurrentUser\My" `
-FriendlyName "證書的友好名稱,在IIS指定的時候顯示Certificate for .NET Core" `
-HashAlgorithm SHA256 `
-KeyUsage DigitalSignature, KeyEncipherment, DataEncipherment `
-TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1")
$certificatePath = 'Cert:\CurrentUser\My\' + ($certificate.ThumbPrint)
# create temporary certificate path
$tmpPath = "C:\tmp"
If(!(test-path $tmpPath))
{
New-Item -ItemType Directory -Force -Path $tmpPath
}
# set certificate password here
$pfxPassword = ConvertTo-SecureString -String "證書的密碼" -Force -AsPlainText
$pfxFilePath = "c:\tmp\證書的名稱.pfx"
$cerFilePath = "c:\tmp\證書的名稱.cer"
# create pfx certificate
Export-PfxCertificate -Cert $certificatePath -FilePath $pfxFilePath -Password $pfxPassword
Export-Certificate -Cert $certificatePath -FilePath $cerFilePath
# import the pfx certificate
Import-PfxCertificate -FilePath $pfxFilePath Cert:\LocalMachine\My -Password $pfxPassword -Exportable
# trust the certificate by importing the pfx certificate into your trusted root
Import-Certificate -FilePath $cerFilePath -CertStoreLocation Cert:\CurrentUser\Root
# optionally delete the physical certificates (don’t delete the pfx file as you need to copy this to your app directory)
# Remove-Item $pfxFilePath
Remove-Item $cerFilePath
然後在服務端配置一下證書:
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://*:5000"
},
"Https": {
"Url": "https://*:5001"
},
"Http2": {
"Url": "http://*:5002",
"Protocols": "Http2"
}
},
"Certificates": {
"Default": {
"Path": "self.cer",
"Password": "123456"
}
}
}
Certificates 配置了預設證書。
那麼客戶端請求一下https://localhost:5001.
返回:
那麼這個時候無論證書驗證是否成功,都返回true:
services.AddGrpcClient<Greeter.GreeterClient>(options =>
{
options.Address = new Uri("https://localhost:5001");
}).ConfigurePrimaryHttpMessageHandler(provider =>
{
var handle = new SocketsHttpHandler();
handle.SslOptions.RemoteCertificateValidationCallback = (a, b, c, d) => true;
return handle;
});
然後就可以訪問了。
下面整理一下異常攔截器,如果我們服務端發生異常,我們希望有某種規律的錯誤發出。
測試異常:
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
throw new Exception("異常資訊");
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
通過異常攔截器過濾一下:
services.AddGrpc(options =>
{
options.EnableDetailedErrors = false;
options.Interceptors.Add<ExceptionInterceptor>();
});
具體異常攔截類:
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
{
try
{
return await base.UnaryServerHandler(request, context, continuation);
}
catch (Exception ex)
{
//log 處理
Metadata entries = new Metadata();
entries.Add("message",ex.Message);
throw new RpcException(new Status(StatusCode.Unknown, "Unknown"), entries);
}
}
除錯:
結
下一節實踐一下用工具生成grpc 程式碼。