重新整理 .net core 實踐篇—————grpc[三十三]

不問前世發表於2021-06-29

前言

簡單整理一下grpc。

正文

什麼是grpc?

  1. 一個遠端過程呼叫框架,可以像類一樣呼叫遠端方法。

這種模式一般來說就是代理模式,然後都是框架自我生成的。

  1. 由google 公司發起並開源,故而前面有個g。

grpc的特點:

  1. 提供幾乎所有主流語言的實現,打破語言隔閡。

  2. 基於http/2,開放協議,收到廣泛的支援,易於實現和整合

http/2 有個特點哈,就是更快,可以瞭解下,後續會專門整理關於http相關的,要等網路原理整理完後。

  1. 預設使用protocol buffers 序列化,效能相較於restful json好很多

  2. 工具鏈成熟,程式碼生成便捷,開箱即用

  3. 支援雙向流式的請求和相應,對批量處理、低延遲場景友好

.net 生態對gRPC的支援情況:

  1. 提供基於httpClient 的原生框架的實現

  2. 提供了原生的ASP .net Core 整合庫

  3. 提供完整的程式碼工具

  4. visual studio 和 visual Stuido Code 提供proto 檔案的智慧提示
    實踐:

service 服務端:

.net core 在服務端使用的包:

  1. Grpc.AspNetCore

客戶端使用的包:

  1. google.protobuf

  2. Grpc.Net.Client

  3. Grpc.Net.ClientFactory

  4. Grpc.Tools

通過工具會生成proto 檔案。

proto 檔案:

  1. 定義包、庫名

  2. 定義服務"service"

  3. 定義輸入輸出模型"message"

gRPC 異常處理:

  1. 使用Grpc.Core.RpcException

  2. 使用Grpc.Core.Interceptors.Interceptor

grpc 的加密方式:

grpc 因為是http2,預設使用https證書。

也可以不使用證書,裡面可以設定,如果設定了,那麼就是不加密的。

  1. 注入服務
services.AddGrpcClient<Greeter.GreeterClient>(options =>
{
	options.Address = new Uri("https://localhost:5001");
});
  1. 測試程式碼
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();
});
  1. 結果

這樣就可以呼叫了。

這裡如果我們使用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 程式碼。

相關文章