共用方式為


針對 .NET 上的 gRPC 進行疑難排解

作者:James Newton-King

注意

這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本

警告

不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支援原則。 如需目前版本,請參閱本文的 .NET 8 版本

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

如需目前版本,請參閱本文的 .NET 8 版本

本文件探討在 .NET 上開發 gRPC 應用程式時發生的常見問題。

用戶端與服務 SSL/TLS 組態不相符

gRPC 範本和範例預設會使用傳輸層安全性 (TLS) 來保護 gRPC 服務。 gRPC 用戶端必須使用安全連線,才能成功呼叫受保護的 gRPC 服務。

您可以在應用程式啟動時寫入的記錄中,驗證 ASP.NET Core gRPC 服務是否正在使用 TLS。 該服務將在 HTTPS 端點上接聽:

info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development

.NET Core 用戶端必須在伺服器位址中使用 https,才能使用安全連線進行呼叫:

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);

所有 gRPC 用戶端實作都支援 TLS。 其他語言的 gRPC 用戶端通常需要以 SslCredentials 設定的通道。 SslCredentials 指定用戶端將使用的憑證,且必須使用該憑證,而不是使用不安全的認證。 如需設定不同 gRPC 用戶端實作以使用 TLS 的範例,請參閱 gRPC 驗證

呼叫具有不受信任/無效憑證的 gRPC 服務

.NET gRPC 用戶端需要服務具有受信任的憑證。 呼叫沒有受信任憑證的 gRPC 服務時,會傳回下列錯誤訊息:

未處理的例外狀況。 System.Net.Http.HttpRequestException:無法建立 SSL 連線,請查看內部例外狀況。 ---> System.Security.Authentication.AuthenticationException:根據驗證程序,遠端憑證無效。

如果您要在本機測試應用程式,但 ASP.NET Core HTTPS 開發憑證不受信任,則可能會看到此錯誤。 如需修正此問題的指示,請參閱信任 Windows 和 macOS上的 ASP.NET Core HTTPS 開發憑證

如果您要在另一部電腦上呼叫 gRPC 服務,但無法信任該憑證,則可以將 gRPC 用戶端設定為忽略無效的憑證。 下列程式碼會使用 HttpClientHandler.ServerCertificateCustomValidationCallback 以允許在沒有受信任憑證的情況下進行呼叫:

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpHandler = handler });
var client = new Greeter.GreeterClient(channel);

gRPC 用戶端處理站允許在沒有受信任憑證的情況下進行呼叫。 使用 ConfigurePrimaryHttpMessageHandler 擴充方法可在用戶端上設定處理常式:


var services = new ServiceCollection();

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ServerCertificateCustomValidationCallback =
            HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

        return handler;
    });

警告

不受信任的憑證只應在應用程式開發期間使用。 生產應用程式應該一律使用有效的憑證。

使用 .NET Core 用戶端呼叫不安全的 gRPC 服務

.NET gRPC 用戶端可以透過在伺服器位址中指定 http 來呼叫不安全的 gRPC 服務。 例如: GrpcChannel.ForAddress("http://localhost:5000")

根據應用程式所使用的 .NET 版本,呼叫不安全的 gRPC 服務還有一些額外需求:

重要

不安全的 gRPC 服務必須裝載在僅限 HTTP/2 的連接埠上。 如需詳細資訊,請參閱 ASP.NET Core 通訊協定交涉

無法在 macOS 上啟動 ASP.NET Core gRPC 應用程式

Kestrel 不支援在 .NET 8 之前的 macOS 上使用透過 TLS 的 HTTP/2。 ASP.NET Core gRPC 範本和範例預設會使用 TLS。 當您嘗試啟動 gRPC 伺服器時,將會看到下列錯誤訊息:

無法在 IPv4 回送介面上繫結至 https://localhost:5001:「由於缺少 ALPN 支援,macOS 不支援透過 TLS 的 HTTP/2。」。

若要在 .NET 7 和更早版本中解決此問題,請將 Kestrel 和 gRPC 用戶端設定為不使用 TLS 的 HTTP/2 。 您只能在開發期間執行此動作。 不使用 TLS 會導致在沒有加密的情況下傳送 gRPC 訊息。 如需詳細資訊,請參閱 Asp.Net Core 7.0:無法在 macOS 上啟動 ASP.NET Core gRPC 應用程式

gRPC C# 資產不是從 .proto 檔案產生的程式碼

具體用戶端和服務基底類別的 gRPC 程式碼產生作業需要從專案參考 protobuf 檔案和工具。 您必須包含:

如需產生 gRPC C# 資產的詳細資訊,請參閱搭配 C# 的 gRPC 服務

裝載 gRPC 服務的 ASP.NET Core Web 應用程式只需要產生的服務基底類別:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>

發出 gRPC 呼叫的 gRPC 用戶端應用程式只需要產生的具體用戶端:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>

WPF 專案無法從 .proto 檔案產生 gRPC C# 資產

WPF 專案存在一個已知問題,導致 gRPC 程式碼產生作業無法正常運作。 在 WPF 專案中參考 Grpc.Tools.proto 檔案產生的任何 gRPC 類型,都會在使用時建立編譯錯誤:

錯誤 CS0246:找不到名為 'MyGrpcServices' 的類型或命名空間名稱 (是否遺漏 using 指示詞或組件參考?)

您可以透過下列方式解決此問題:

  1. 建立新的 .NET Core 類別庫專案。
  2. 在新專案中,新增參考以啟用.proto 檔案產生 C# 程式碼的功能:
  3. 在 WPF 應用程式中,新增新專案的參考。

WPF 應用程式可以使用新類別庫專案中 gRPC 產生的類型。

呼叫裝載在子目錄中的 gRPC 服務

警告

許多第三方 gRPC 工具不支援子目錄中裝載的服務。 請考慮尋找將 gRPC 裝載為根目錄的方法。

發出 gRPC 呼叫時,會忽略 gRPC 通道位址的路徑元件。 例如,在路由傳送服務的 gRPC 呼叫時,GrpcChannel.ForAddress("https://localhost:5001/ignored_path") 不會使用 ignored_path

由於 gRPC 具有標準化的規範位址結構,因此會忽略位址路徑。 gRPC 位址結合了套件、服務和方法名稱:https://localhost:5001/PackageName.ServiceName/MethodName

在某些情況下,應用程式需要包含具有 gRPC 呼叫的路徑。 例如,當 ASP.NET Core gRPC 應用程式裝載於 IIS 目錄,且該目錄必須包含在要求中時。 需要有路徑時,可以使用下列指定的自訂 SubdirectoryHandler 將其新增至 gRPC 呼叫:

public class SubdirectoryHandler : DelegatingHandler
{
    private readonly string _subdirectory;

    public SubdirectoryHandler(HttpMessageHandler innerHandler, string subdirectory)
        : base(innerHandler)
    {
        _subdirectory = subdirectory;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var old = request.RequestUri;

        var url = $"{old.Scheme}://{old.Host}:{old.Port}";
        url += $"{_subdirectory}{request.RequestUri.AbsolutePath}";
        request.RequestUri = new Uri(url, UriKind.Absolute);

        return base.SendAsync(request, cancellationToken);
    }
}

SubdirectoryHandler 會在建立 gRPC 通道時使用。

var handler = new SubdirectoryHandler(new HttpClientHandler(), "/MyApp");

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpHandler = handler });
var client = new Greeter.GreeterClient(channel);

var reply = await client.SayHelloAsync(
                  new HelloRequest { Name = "GreeterClient" });

上述 程式碼:

  • 使用路徑 /MyApp 建立 SubdirectoryHandler
  • 設定通道以使用 SubdirectoryHandler
  • 使用 SayHelloAsync 呼叫 gRPC 服務。 gRPC 呼叫會傳送至 https://localhost:5001/MyApp/greet.Greeter/SayHello

或者,您可以使用 AddHttpMessageHandler 為用戶端處理站設定 SubdirectoryHandler

將 gRPC 用戶端設定為使用 HTTP/3

.NET gRPC 用戶端支援搭配 .NET 6 或更新版本使用 HTTP/3。 如果伺服器將指出伺服器支援 HTTP/3 的 alt-svc 回應標頭傳送給用戶端,則用戶端會將其連線自動升級至 HTTP/3。 如需詳細資訊,請參閱搭配 ASP.NET Core Kestrel 網頁伺服器使用 HTTP/3

DelegatingHandler 可用來強制 gRPC 用戶端使用 HTTP/3。 強制使用 HTTP/3 可避免升級要求的額外負荷。 透過類似於下列內容的程式碼強制使用 HTTP/3:

public class Http3Handler : DelegatingHandler
{
    public Http3Handler() { }
    public Http3Handler(HttpMessageHandler innerHandler) : base(innerHandler) { }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Version = HttpVersion.Version30;
        request.VersionPolicy = HttpVersionPolicy.RequestVersionExact;

        return base.SendAsync(request, cancellationToken);
    }
}

Http3Handler 會在建立 gRPC 通道時使用。 下列程式碼會建立設定為使用 Http3Handler 的通道。

var handler = new Http3Handler(new HttpClientHandler());

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpHandler = handler });
var client = new Greeter.GreeterClient(channel);

var reply = await client.SayHelloAsync(
                  new HelloRequest { Name = "GreeterClient" });

或者,您可以使用 AddHttpMessageHandler 為用戶端處理站設定 Http3Handler

在 Alpine Linux 上建置 gRPC

Grpc.Tools 套件會使用稱為 protoc 的配套原生二進位檔案,.proto 檔案產生 .NET 類型。 若要在 Grpc.Tools 中的原生二進位檔案不支援的平台 (例如 Alpine Linux) 上建置 gRPC 應用程式,則需要執行其他步驟。

預先產生程式碼

其中一個解決方案是預先產生程式碼。

  1. .proto 檔案和 Grpc.Tools 套件參考移至新專案。
  2. 將專案發行為 NuGet 套件,並將其上傳至 NuGet 摘要。
  3. 更新應用程式以參考 NuGet 套件。

透過上述步驟,不再需要 Grpc.Tools 來建置應用程式,因為程式碼可預先產生。

自訂 Grpc.Tools 原生二進位檔案

Grpc.Tools 支援使用自訂的原生二進位檔案。 此功能可讓 gRPC 工具在其配套原生二進位檔案不支援的環境中執行。

建置或取得 protocgrpc_csharp_plugin 原生二進位檔案,並將 Grpc.Tools 設定為使用這些檔案。 藉由設定下列環境變數來設定原生二進位檔案:

  • PROTOBUF_PROTOC:通訊協定緩衝區編譯器的完整路徑
  • GRPC_PROTOC_PLUGIN:grpc_csharp_plugin 的完整路徑

對於 Alpine Linux,可從 https://pkgs.alpinelinux.org/ 取得社群提供用於通訊協定緩衝區編譯器和 gRPC 外掛程式的套件。

# Build or install the binaries for your architecture.

# For Alpine Linux, the grpc-plugins package can be used.
# See https://pkgs.alpinelinux.org/package/edge/community/x86_64/grpc-plugins
apk add grpc-plugins  # Alpine Linux specific package installer

# Set environment variables for the built/installed protoc
# and grpc_csharp_plugin binaries
export PROTOBUF_PROTOC=/usr/bin/protoc
export GRPC_PROTOC_PLUGIN=/usr/bin/grpc_csharp_plugin

# When dotnet build runs, the Grpc.Tools NuGet package
# uses the binaries pointed to by the environment variables.
dotnet build

如需搭配不支援的結構使用 Grpc.Tools 的詳細資訊,請參閱 gRPC 組建整合文件

來自 HttpClient.Timeout 的 gRPC 呼叫逾時

HttpClient 會預設 100 秒的逾時。 如果 GrpcChannel 設定為使用 HttpClient,當長時間執行的 gRPC 串流呼叫未在逾時限制內完成時,這些呼叫就會取消。

System.OperationCanceledException: The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.

有幾種方法可以修正此錯誤。 第一個是將 HttpClient.Timeout 設定為較大的值。 Timeout.InfiniteTimeSpan 會停用逾時:

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var httpClient = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan };
var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpClient = httpClient });
var client = new Greeter.GreeterClient(channel);

或者,避免建立 HttpClient,改為設定 GrpcChannel.HttpHandler

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpHandler = handler });
var client = new Greeter.GreeterClient(channel);

本文件探討在 .NET 上開發 gRPC 應用程式時發生的常見問題。

用戶端與服務 SSL/TLS 組態不相符

gRPC 範本和範例預設會使用傳輸層安全性 (TLS) 來保護 gRPC 服務。 gRPC 用戶端必須使用安全連線,才能成功呼叫受保護的 gRPC 服務。

您可以在應用程式啟動時寫入的記錄中,驗證 ASP.NET Core gRPC 服務是否正在使用 TLS。 該服務將在 HTTPS 端點上接聽:

info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development

.NET Core 用戶端必須在伺服器位址中使用 https,才能使用安全連線進行呼叫:

static async Task Main(string[] args)
{
    // The port number(5001) must match the port of the gRPC server.
    var channel = GrpcChannel.ForAddress("https://localhost:5001");
    var client = new Greet.GreeterClient(channel);
}

所有 gRPC 用戶端實作都支援 TLS。 其他語言的 gRPC 用戶端通常需要以 SslCredentials 設定的通道。 SslCredentials 指定用戶端將使用的憑證,且必須使用該憑證,而不是使用不安全的認證。 如需設定不同 gRPC 用戶端實作以使用 TLS 的範例,請參閱 gRPC 驗證

呼叫具有不受信任/無效憑證的 gRPC 服務

.NET gRPC 用戶端需要服務具有受信任的憑證。 呼叫沒有受信任憑證的 gRPC 服務時,會傳回下列錯誤訊息:

未處理的例外狀況。 System.Net.Http.HttpRequestException:無法建立 SSL 連線,請查看內部例外狀況。 ---> System.Security.Authentication.AuthenticationException:根據驗證程序,遠端憑證無效。

如果您要在本機測試應用程式,但 ASP.NET Core HTTPS 開發憑證不受信任,則可能會看到此錯誤。 如需修正此問題的指示,請參閱信任 Windows 和 macOS上的 ASP.NET Core HTTPS 開發憑證

如果您要在另一部電腦上呼叫 gRPC 服務,但無法信任該憑證,則可以將 gRPC 用戶端設定為忽略無效的憑證。 下列程式碼會使用 HttpClientHandler.ServerCertificateCustomValidationCallback 以允許在沒有受信任憑證的情況下進行呼叫:

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpHandler = handler });
var client = new Greet.GreeterClient(channel);

gRPC 用戶端處理站允許在沒有受信任憑證的情況下進行呼叫。 使用 ConfigurePrimaryHttpMessageHandler 擴充方法可在用戶端上設定處理常式:

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ServerCertificateCustomValidationCallback = 
            HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

        return handler;
    });

警告

不受信任的憑證只應在應用程式開發期間使用。 生產應用程式應該一律使用有效的憑證。

使用 .NET Core 用戶端呼叫不安全的 gRPC 服務

.NET gRPC 用戶端可以透過在伺服器位址中指定 http 來呼叫不安全的 gRPC 服務。 例如: GrpcChannel.ForAddress("http://localhost:5000")

根據應用程式所使用的 .NET 版本,呼叫不安全的 gRPC 服務還有一些額外需求:

  • .NET 5 或更新版本須使用 Grpc.Net.Client 2.32.0 版或更新版本。

  • .NET Core 3.x 須設定額外的組態。 應用程式必須將 System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport 參數設定為 true

    // This switch must be set before creating the GrpcChannel/HttpClient.
    AppContext.SetSwitch(
        "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
    
    // The port number(5000) must match the port of the gRPC server.
    var channel = GrpcChannel.ForAddress("http://localhost:5000");
    var client = new Greet.GreeterClient(channel);
    

只有 .NET Core 3.x 才需要 System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport 參數。 該參數不會在 .NET 5 中執行任何動作,也不是必要項目。

重要

不安全的 gRPC 服務必須裝載在僅限 HTTP/2 的連接埠上。 如需詳細資訊,請參閱 ASP.NET Core 通訊協定交涉

無法在 macOS 上啟動 ASP.NET Core gRPC 應用程式

Kestrel 不支援在 .NET 8 之前的 macOS 上使用透過 TLS 的 HTTP/2。 ASP.NET Core gRPC 範本和範例預設會使用 TLS。 當您嘗試啟動 gRPC 伺服器時,將會看到下列錯誤訊息:

無法在 IPv4 回送介面上繫結至 https://localhost:5001:「由於缺少 ALPN 支援,macOS 不支援透過 TLS 的 HTTP/2。」。

若要在 .NET 7 和更早版本中解決此問題,請將 Kestrel 和 gRPC 用戶端設定為不使用 TLS 的 HTTP/2 。 您只能在開發期間執行此動作。 不使用 TLS 會導致在沒有加密的情況下傳送 gRPC 訊息。

Kestrel 必須在 Program.cs 中設定不使用 TLS 的 HTTP/2 端點:

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    // Setup a HTTP/2 endpoint without TLS.
    options.ListenLocalhost(<5287>, o => o.Protocols =
        HttpProtocols.Http2);
});
  • 在上述的程式碼中,將 localhost 連接埠號碼 5287 取代為 gRPC 服務專案內 Properties/launchSettings.json 中指定的 HTTP (而不是 HTTPS) 連接埠號碼。

在未使用 TLS 的情況下設定 HTTP/2 端點時,必須將端點的 ListenOptions.Protocols 設定為 HttpProtocols.Http2。 由於需要 TLS 才能交涉 HTTP/2,因此無法使用 HttpProtocols.Http1AndHttp2。 如果未使用 TLS,與端點的所有連線都會預設使用 HTTP/1.1,而 gRPC 呼叫會失敗。

gRPC 用戶端也必須設定為不使用 TLS。 如需詳細資訊,請參閱使用 .NET Core 用戶端呼叫不安全的 gRPC 服務

警告

不使用 TLS 的 HTTP/2 只應在應用程式開發期間使用。 生產應用程式應該一律使用傳輸安全性。 如需詳細資訊,請參閱 ASP.NET Core 的 gRPC 安全性考量

gRPC C# 資產不是從 .proto 檔案產生的程式碼

具體用戶端和服務基底類別的 gRPC 程式碼產生作業需要從專案參考 protobuf 檔案和工具。 您必須包含:

如需產生 gRPC C# 資產的詳細資訊,請參閱搭配 C# 的 gRPC 服務

裝載 gRPC 服務的 ASP.NET Core Web 應用程式只需要產生的服務基底類別:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>

發出 gRPC 呼叫的 gRPC 用戶端應用程式只需要產生的具體用戶端:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>

WPF 專案無法從 .proto 檔案產生 gRPC C# 資產

WPF 專案存在一個已知問題,導致 gRPC 程式碼產生作業無法正常運作。 在 WPF 專案中參考 Grpc.Tools.proto 檔案產生的任何 gRPC 類型,都會在使用時建立編譯錯誤:

錯誤 CS0246:找不到名為 'MyGrpcServices' 的類型或命名空間名稱 (是否遺漏 using 指示詞或組件參考?)

您可以透過下列方式解決此問題:

  1. 建立新的 .NET Core 類別庫專案。
  2. 在新專案中,新增參考以啟用.proto 檔案產生 C# 程式碼的功能:
  3. 在 WPF 應用程式中,新增新專案的參考。

WPF 應用程式可以使用新類別庫專案中 gRPC 產生的類型。

呼叫裝載在子目錄中的 gRPC 服務

警告

許多第三方 gRPC 工具不支援子目錄中裝載的服務。 請考慮尋找將 gRPC 裝載為根目錄的方法。

發出 gRPC 呼叫時,會忽略 gRPC 通道位址的路徑元件。 例如,在路由傳送服務的 gRPC 呼叫時,GrpcChannel.ForAddress("https://localhost:5001/ignored_path") 不會使用 ignored_path

由於 gRPC 具有標準化的規範位址結構,因此會忽略位址路徑。 gRPC 位址結合了套件、服務和方法名稱:https://localhost:5001/PackageName.ServiceName/MethodName

在某些情況下,應用程式需要包含具有 gRPC 呼叫的路徑。 例如,當 ASP.NET Core gRPC 應用程式裝載於 IIS 目錄,且該目錄必須包含在要求中時。 需要有路徑時,可以使用下列指定的自訂 SubdirectoryHandler 將其新增至 gRPC 呼叫:

/// <summary>
/// A delegating handler that adds a subdirectory to the URI of gRPC requests.
/// </summary>
public class SubdirectoryHandler : DelegatingHandler
{
    private readonly string _subdirectory;

    public SubdirectoryHandler(HttpMessageHandler innerHandler, string subdirectory)
        : base(innerHandler)
    {
        _subdirectory = subdirectory;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var old = request.RequestUri;

        var url = $"{old.Scheme}://{old.Host}:{old.Port}";
        url += $"{_subdirectory}{request.RequestUri.AbsolutePath}";
        request.RequestUri = new Uri(url, UriKind.Absolute);

        return base.SendAsync(request, cancellationToken);
    }
}

SubdirectoryHandler 會在建立 gRPC 通道時使用。

var handler = new SubdirectoryHandler(new HttpClientHandler(), "/MyApp");

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpHandler = handler });
var client = new Greet.GreeterClient(channel);

var reply = await client.SayHelloAsync(new HelloRequest { Name = ".NET" });

上述 程式碼:

  • 使用路徑 /MyApp 建立 SubdirectoryHandler
  • 設定通道以使用 SubdirectoryHandler
  • 使用 SayHelloAsync 呼叫 gRPC 服務。 gRPC 呼叫會傳送至 https://localhost:5001/MyApp/greet.Greeter/SayHello

或者,您可以使用 AddHttpMessageHandler 為用戶端處理站設定 SubdirectoryHandler

將 gRPC 用戶端設定為使用 HTTP/3

.NET gRPC 用戶端支援搭配 .NET 6 或更新版本使用 HTTP/3。 如果伺服器將指出伺服器支援 HTTP/3 的 alt-svc 回應標頭傳送給用戶端,則用戶端會將其連線自動升級至 HTTP/3。 如需如何在伺服器上啟用 HTTP/3 的資訊,請參閱搭配 ASP.NET Core Kestrel 網頁伺服器使用 HTTP/3

預設會啟用 .NET 8 中的 HTTP/3 支援。 .NET 6 和 .NET 7 中的 HTTP/3 支援必須透過專案檔中的組態旗標來啟用:

<ItemGroup>
  <RuntimeHostConfigurationOption Include="System.Net.SocketsHttpHandler.Http3Support" Value="true" />
</ItemGroup>

也可以使用 AppContext.SetSwitch 來設定 System.Net.SocketsHttpHandler.Http3Support

DelegatingHandler 可用來強制 gRPC 用戶端使用 HTTP/3。 強制使用 HTTP/3 可避免升級要求的額外負荷。 透過類似於下列內容的程式碼強制使用 HTTP/3:

/// <summary>
/// A delegating handler that changes the request HTTP version to HTTP/3.
/// </summary>
public class Http3Handler : DelegatingHandler
{
    public Http3Handler() { }
    public Http3Handler(HttpMessageHandler innerHandler) : base(innerHandler) { }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Version = HttpVersion.Version30;
        request.VersionPolicy = HttpVersionPolicy.RequestVersionExact;

        return base.SendAsync(request, cancellationToken);
    }
}

Http3Handler 會在建立 gRPC 通道時使用。 下列程式碼會建立設定為使用 Http3Handler 的通道。

var handler = new Http3Handler(new HttpClientHandler());

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpHandler = handler });
var client = new Greet.GreeterClient(channel);

var reply = await client.SayHelloAsync(new HelloRequest { Name = ".NET" });

或者,您可以使用 AddHttpMessageHandler 為用戶端處理站設定 Http3Handler

在 Alpine Linux 上建置 gRPC

Grpc.Tools 套件會使用稱為 protoc 的配套原生二進位檔案,.proto 檔案產生 .NET 類型。 若要在 Grpc.Tools 中的原生二進位檔案不支援的平台 (例如 Alpine Linux) 上建置 gRPC 應用程式,則需要執行其他步驟。

預先產生程式碼

其中一個解決方案是預先產生程式碼。

  1. .proto 檔案和 Grpc.Tools 套件參考移至新專案。
  2. 將專案發行為 NuGet 套件,並將其上傳至 NuGet 摘要。
  3. 更新應用程式以參考 NuGet 套件。

透過上述步驟,不再需要 Grpc.Tools 來建置應用程式,因為程式碼可預先產生。

自訂 Grpc.Tools 原生二進位檔案

Grpc.Tools 支援使用自訂的原生二進位檔案。 此功能可讓 gRPC 工具在其配套原生二進位檔案不支援的環境中執行。

建置或取得 protocgrpc_csharp_plugin 原生二進位檔案,並將 Grpc.Tools 設定為使用這些檔案。 藉由設定下列環境變數來設定原生二進位檔案:

  • PROTOBUF_PROTOC:通訊協定緩衝區編譯器的完整路徑
  • GRPC_PROTOC_PLUGIN:grpc_csharp_plugin 的完整路徑

對於 Alpine Linux,可從 https://pkgs.alpinelinux.org/ 取得社群提供用於通訊協定緩衝區編譯器和 gRPC 外掛程式的套件。

# Build or install the binaries for your architecture.

# For Alpine Linux, the grpc-plugins package can be used.
#  See https://pkgs.alpinelinux.org/package/edge/community/x86_64/grpc-plugins
apk add grpc-plugins  # Alpine Linux specific package installer

# Set environment variables for the built/installed protoc
# and grpc_csharp_plugin binaries
export PROTOBUF_PROTOC=/usr/bin/protoc
export GRPC_PROTOC_PLUGIN=/usr/bin/grpc_csharp_plugin

# When dotnet build runs, the Grpc.Tools NuGet package
# uses the binaries pointed to by the environment variables.
dotnet build

如需搭配不支援的結構使用 Grpc.Tools 的詳細資訊,請參閱 gRPC 組建整合文件

來自 HttpClient.Timeout 的 gRPC 呼叫逾時

HttpClient 會預設 100 秒的逾時。 如果 GrpcChannel 設定為使用 HttpClient,當長時間執行的 gRPC 串流呼叫未在逾時限制內完成時,這些呼叫就會取消。

System.OperationCanceledException: The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.

有幾種方法可以修正此錯誤。 第一個是將 HttpClient.Timeout 設定為較大的值。 Timeout.InfiniteTimeSpan 會停用逾時:

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var httpClient = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan };
var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpClient = httpClient });
var client = new Greeter.GreeterClient(channel);

或者,避免建立 HttpClient,改為設定 GrpcChannel.HttpHandler

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpHandler = handler });
var client = new Greeter.GreeterClient(channel);

本文件探討在 .NET 上開發 gRPC 應用程式時發生的常見問題。

用戶端與服務 SSL/TLS 組態不相符

gRPC 範本和範例預設會使用傳輸層安全性 (TLS) 來保護 gRPC 服務。 gRPC 用戶端必須使用安全連線,才能成功呼叫受保護的 gRPC 服務。

您可以在應用程式啟動時寫入的記錄中,驗證 ASP.NET Core gRPC 服務是否正在使用 TLS。 該服務將在 HTTPS 端點上接聽:

info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development

.NET Core 用戶端必須在伺服器位址中使用 https,才能使用安全連線進行呼叫:

static async Task Main(string[] args)
{
    // The port number(5001) must match the port of the gRPC server.
    var channel = GrpcChannel.ForAddress("https://localhost:5001");
    var client = new Greet.GreeterClient(channel);
}

所有 gRPC 用戶端實作都支援 TLS。 其他語言的 gRPC 用戶端通常需要以 SslCredentials 設定的通道。 SslCredentials 指定用戶端將使用的憑證,且必須使用該憑證,而不是使用不安全的認證。 如需設定不同 gRPC 用戶端實作以使用 TLS 的範例,請參閱 gRPC 驗證

呼叫具有不受信任/無效憑證的 gRPC 服務

.NET gRPC 用戶端需要服務具有受信任的憑證。 呼叫沒有受信任憑證的 gRPC 服務時,會傳回下列錯誤訊息:

未處理的例外狀況。 System.Net.Http.HttpRequestException:無法建立 SSL 連線,請查看內部例外狀況。 ---> System.Security.Authentication.AuthenticationException:根據驗證程序,遠端憑證無效。

如果您要在本機測試應用程式,但 ASP.NET Core HTTPS 開發憑證不受信任,則可能會看到此錯誤。 如需修正此問題的指示,請參閱信任 Windows 和 macOS上的 ASP.NET Core HTTPS 開發憑證

如果您要在另一部電腦上呼叫 gRPC 服務,但無法信任該憑證,則可以將 gRPC 用戶端設定為忽略無效的憑證。 下列程式碼會使用 HttpClientHandler.ServerCertificateCustomValidationCallback 以允許在沒有受信任憑證的情況下進行呼叫:

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpHandler = handler });
var client = new Greet.GreeterClient(channel);

gRPC 用戶端處理站允許在沒有受信任憑證的情況下進行呼叫。 使用 ConfigurePrimaryHttpMessageHandler 擴充方法可在用戶端上設定處理常式:

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ServerCertificateCustomValidationCallback = 
            HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

        return handler;
    });

警告

不受信任的憑證只應在應用程式開發期間使用。 生產應用程式應該一律使用有效的憑證。

使用 .NET Core 用戶端呼叫不安全的 gRPC 服務

.NET gRPC 用戶端可以透過在伺服器位址中指定 http 來呼叫不安全的 gRPC 服務。 例如: GrpcChannel.ForAddress("http://localhost:5000")

根據應用程式所使用的 .NET 版本,呼叫不安全的 gRPC 服務還有一些額外需求:

  • .NET 5 或更新版本須使用 Grpc.Net.Client 2.32.0 版或更新版本。

  • .NET Core 3.x 須設定額外的組態。 應用程式必須將 System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport 參數設定為 true

    // This switch must be set before creating the GrpcChannel/HttpClient.
    AppContext.SetSwitch(
        "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
    
    // The port number(5000) must match the port of the gRPC server.
    var channel = GrpcChannel.ForAddress("http://localhost:5000");
    var client = new Greet.GreeterClient(channel);
    

只有 .NET Core 3.x 才需要 System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport 參數。 該參數不會在 .NET 5 中執行任何動作,也不是必要項目。

重要

不安全的 gRPC 服務必須裝載在僅限 HTTP/2 的連接埠上。 如需詳細資訊,請參閱 ASP.NET Core 通訊協定交涉

無法在 macOS 上啟動 ASP.NET Core gRPC 應用程式

Kestrel 不支援在 .NET 8 之前的 macOS 上使用透過 TLS 的 HTTP/2。 ASP.NET Core gRPC 範本和範例預設會使用 TLS。 當您嘗試啟動 gRPC 伺服器時,將會看到下列錯誤訊息:

無法在 IPv4 回送介面上繫結至 https://localhost:5001:「由於缺少 ALPN 支援,macOS 不支援透過 TLS 的 HTTP/2。」。

若要在 .NET 7 和更早版本中解決此問題,請將 Kestrel 和 gRPC 用戶端設定為不使用 TLS 的 HTTP/2 。 您只能在開發期間執行此動作。 不使用 TLS 會導致在沒有加密的情況下傳送 gRPC 訊息。

Kestrel 必須在 Program.cs 中設定不使用 TLS 的 HTTP/2 端點:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(options =>
            {
                // Setup a HTTP/2 endpoint without TLS.
                options.ListenLocalhost(5000, o => o.Protocols = 
                    HttpProtocols.Http2);
            });
            webBuilder.UseStartup<Startup>();
        });

在未使用 TLS 的情況下設定 HTTP/2 端點時,必須將端點的 ListenOptions.Protocols 設定為 HttpProtocols.Http2。 由於需要 TLS 才能交涉 HTTP/2,因此無法使用 HttpProtocols.Http1AndHttp2。 如果未使用 TLS,與端點的所有連線都會預設使用 HTTP/1.1,而 gRPC 呼叫會失敗。

gRPC 用戶端也必須設定為不使用 TLS。 如需詳細資訊,請參閱使用 .NET Core 用戶端呼叫不安全的 gRPC 服務

警告

不使用 TLS 的 HTTP/2 只應在應用程式開發期間使用。 生產應用程式應該一律使用傳輸安全性。 如需詳細資訊,請參閱 ASP.NET Core 的 gRPC 安全性考量

gRPC C# 資產不是從 .proto 檔案產生的程式碼

具體用戶端和服務基底類別的 gRPC 程式碼產生作業需要從專案參考 protobuf 檔案和工具。 您必須包含:

如需產生 gRPC C# 資產的詳細資訊,請參閱搭配 C# 的 gRPC 服務

裝載 gRPC 服務的 ASP.NET Core Web 應用程式只需要產生的服務基底類別:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>

發出 gRPC 呼叫的 gRPC 用戶端應用程式只需要產生的具體用戶端:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>

WPF 專案無法從 .proto 檔案產生 gRPC C# 資產

WPF 專案存在一個已知問題,導致 gRPC 程式碼產生作業無法正常運作。 在 WPF 專案中參考 Grpc.Tools.proto 檔案產生的任何 gRPC 類型,都會在使用時建立編譯錯誤:

錯誤 CS0246:找不到名為 'MyGrpcServices' 的類型或命名空間名稱 (是否遺漏 using 指示詞或組件參考?)

您可以透過下列方式解決此問題:

  1. 建立新的 .NET Core 類別庫專案。
  2. 在新專案中,新增參考以啟用.proto 檔案產生 C# 程式碼的功能:
  3. 在 WPF 應用程式中,新增新專案的參考。

WPF 應用程式可以使用新類別庫專案中 gRPC 產生的類型。

呼叫裝載在子目錄中的 gRPC 服務

警告

許多第三方 gRPC 工具不支援子目錄中裝載的服務。 請考慮尋找將 gRPC 裝載為根目錄的方法。

發出 gRPC 呼叫時,會忽略 gRPC 通道位址的路徑元件。 例如,在路由傳送服務的 gRPC 呼叫時,GrpcChannel.ForAddress("https://localhost:5001/ignored_path") 不會使用 ignored_path

由於 gRPC 具有標準化的規範位址結構,因此會忽略位址路徑。 gRPC 位址結合了套件、服務和方法名稱:https://localhost:5001/PackageName.ServiceName/MethodName

在某些情況下,應用程式需要包含具有 gRPC 呼叫的路徑。 例如,當 ASP.NET Core gRPC 應用程式裝載於 IIS 目錄,且該目錄必須包含在要求中時。 需要有路徑時,可以使用下列指定的自訂 SubdirectoryHandler 將其新增至 gRPC 呼叫:

/// <summary>
/// A delegating handler that adds a subdirectory to the URI of gRPC requests.
/// </summary>
public class SubdirectoryHandler : DelegatingHandler
{
    private readonly string _subdirectory;

    public SubdirectoryHandler(HttpMessageHandler innerHandler, string subdirectory)
        : base(innerHandler)
    {
        _subdirectory = subdirectory;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var old = request.RequestUri;

        var url = $"{old.Scheme}://{old.Host}:{old.Port}";
        url += $"{_subdirectory}{request.RequestUri.AbsolutePath}";
        request.RequestUri = new Uri(url, UriKind.Absolute);

        return base.SendAsync(request, cancellationToken);
    }
}

SubdirectoryHandler 會在建立 gRPC 通道時使用。

var handler = new SubdirectoryHandler(new HttpClientHandler(), "/MyApp");

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpHandler = handler });
var client = new Greet.GreeterClient(channel);

var reply = await client.SayHelloAsync(new HelloRequest { Name = ".NET" });

上述 程式碼:

  • 使用路徑 /MyApp 建立 SubdirectoryHandler
  • 設定通道以使用 SubdirectoryHandler
  • 使用 SayHelloAsync 呼叫 gRPC 服務。 gRPC 呼叫會傳送至 https://localhost:5001/MyApp/greet.Greeter/SayHello

或者,您可以使用 AddHttpMessageHandler 為用戶端處理站設定 SubdirectoryHandler

來自 HttpClient.Timeout 的 gRPC 呼叫逾時

HttpClient 會預設 100 秒的逾時。 如果 GrpcChannel 設定為使用 HttpClient,當長時間執行的 gRPC 串流呼叫未在逾時限制內完成時,這些呼叫就會取消。

System.OperationCanceledException: The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.

有幾種方法可以修正此錯誤。 第一個是將 HttpClient.Timeout 設定為較大的值。 Timeout.InfiniteTimeSpan 會停用逾時:

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var httpClient = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan };
var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpClient = httpClient });
var client = new Greeter.GreeterClient(channel);

或者,避免建立 HttpClient,改為設定 GrpcChannel.HttpHandler

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpHandler = handler });
var client = new Greeter.GreeterClient(channel);

本文件探討在 .NET 上開發 gRPC 應用程式時發生的常見問題。

用戶端與服務 SSL/TLS 組態不相符

gRPC 範本和範例預設會使用傳輸層安全性 (TLS) 來保護 gRPC 服務。 gRPC 用戶端必須使用安全連線,才能成功呼叫受保護的 gRPC 服務。

您可以在應用程式啟動時寫入的記錄中,驗證 ASP.NET Core gRPC 服務是否正在使用 TLS。 該服務將在 HTTPS 端點上接聽:

info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development

.NET Core 用戶端必須在伺服器位址中使用 https,才能使用安全連線進行呼叫:

static async Task Main(string[] args)
{
    // The port number(5001) must match the port of the gRPC server.
    var channel = GrpcChannel.ForAddress("https://localhost:5001");
    var client = new Greet.GreeterClient(channel);
}

所有 gRPC 用戶端實作都支援 TLS。 其他語言的 gRPC 用戶端通常需要以 SslCredentials 設定的通道。 SslCredentials 指定用戶端將使用的憑證,且必須使用該憑證,而不是使用不安全的認證。 如需設定不同 gRPC 用戶端實作以使用 TLS 的範例,請參閱 gRPC 驗證

呼叫具有不受信任/無效憑證的 gRPC 服務

.NET gRPC 用戶端需要服務具有受信任的憑證。 呼叫沒有受信任憑證的 gRPC 服務時,會傳回下列錯誤訊息:

未處理的例外狀況。 System.Net.Http.HttpRequestException:無法建立 SSL 連線,請查看內部例外狀況。 ---> System.Security.Authentication.AuthenticationException:根據驗證程序,遠端憑證無效。

如果您要在本機測試應用程式,但 ASP.NET Core HTTPS 開發憑證不受信任,則可能會看到此錯誤。 如需修正此問題的指示,請參閱信任 Windows 和 macOS上的 ASP.NET Core HTTPS 開發憑證

如果您要在另一部電腦上呼叫 gRPC 服務,但無法信任該憑證,則可以將 gRPC 用戶端設定為忽略無效的憑證。 下列程式碼會使用 HttpClientHandler.ServerCertificateCustomValidationCallback 以允許在沒有受信任憑證的情況下進行呼叫:

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpHandler = handler });
var client = new Greet.GreeterClient(channel);

gRPC 用戶端處理站允許在沒有受信任憑證的情況下進行呼叫。 使用 ConfigurePrimaryHttpMessageHandler 擴充方法可在用戶端上設定處理常式:

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ServerCertificateCustomValidationCallback = 
            HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

        return handler;
    });

警告

不受信任的憑證只應在應用程式開發期間使用。 生產應用程式應該一律使用有效的憑證。

使用 .NET Core 用戶端呼叫不安全的 gRPC 服務

.NET gRPC 用戶端可以透過在伺服器位址中指定 http 來呼叫不安全的 gRPC 服務。 例如: GrpcChannel.ForAddress("http://localhost:5000")

根據應用程式所使用的 .NET 版本,呼叫不安全的 gRPC 服務還有一些額外需求:

  • .NET 5 或更新版本須使用 Grpc.Net.Client 2.32.0 版或更新版本。

  • .NET Core 3.x 須設定額外的組態。 應用程式必須將 System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport 參數設定為 true

    // This switch must be set before creating the GrpcChannel/HttpClient.
    AppContext.SetSwitch(
        "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
    
    // The port number(5000) must match the port of the gRPC server.
    var channel = GrpcChannel.ForAddress("http://localhost:5000");
    var client = new Greet.GreeterClient(channel);
    

只有 .NET Core 3.x 才需要 System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport 參數。 該參數不會在 .NET 5 中執行任何動作,也不是必要項目。

重要

不安全的 gRPC 服務必須裝載在僅限 HTTP/2 的連接埠上。 如需詳細資訊,請參閱 ASP.NET Core 通訊協定交涉

無法在 macOS 上啟動 ASP.NET Core gRPC 應用程式

Kestrel 不支援在 .NET 8 之前的 macOS 上使用透過 TLS 的 HTTP/2。 ASP.NET Core gRPC 範本和範例預設會使用 TLS。 當您嘗試啟動 gRPC 伺服器時,將會看到下列錯誤訊息:

無法在 IPv4 回送介面上繫結至 https://localhost:5001:「由於缺少 ALPN 支援,macOS 不支援透過 TLS 的 HTTP/2。」。

若要在 .NET 7 和更早版本中解決此問題,請將 Kestrel 和 gRPC 用戶端設定為不使用 TLS 的 HTTP/2 。 您只能在開發期間執行此動作。 不使用 TLS 會導致在沒有加密的情況下傳送 gRPC 訊息。

Kestrel 必須在 Program.cs 中設定不使用 TLS 的 HTTP/2 端點:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(options =>
            {
                // Setup a HTTP/2 endpoint without TLS.
                options.ListenLocalhost(5000, o => o.Protocols = 
                    HttpProtocols.Http2);
            });
            webBuilder.UseStartup<Startup>();
        });

在未使用 TLS 的情況下設定 HTTP/2 端點時,必須將端點的 ListenOptions.Protocols 設定為 HttpProtocols.Http2。 由於需要 TLS 才能交涉 HTTP/2,因此無法使用 HttpProtocols.Http1AndHttp2。 如果未使用 TLS,與端點的所有連線都會預設使用 HTTP/1.1,而 gRPC 呼叫會失敗。

gRPC 用戶端也必須設定為不使用 TLS。 如需詳細資訊,請參閱使用 .NET Core 用戶端呼叫不安全的 gRPC 服務

警告

不使用 TLS 的 HTTP/2 只應在應用程式開發期間使用。 生產應用程式應該一律使用傳輸安全性。 如需詳細資訊,請參閱 ASP.NET Core 的 gRPC 安全性考量

gRPC C# 資產不是從 .proto 檔案產生的程式碼

具體用戶端和服務基底類別的 gRPC 程式碼產生作業需要從專案參考 protobuf 檔案和工具。 您必須包含:

如需產生 gRPC C# 資產的詳細資訊,請參閱搭配 C# 的 gRPC 服務

裝載 gRPC 服務的 ASP.NET Core Web 應用程式只需要產生的服務基底類別:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>

發出 gRPC 呼叫的 gRPC 用戶端應用程式只需要產生的具體用戶端:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>

WPF 專案無法從 .proto 檔案產生 gRPC C# 資產

WPF 專案存在一個已知問題,導致 gRPC 程式碼產生作業無法正常運作。 在 WPF 專案中參考 Grpc.Tools.proto 檔案產生的任何 gRPC 類型,都會在使用時建立編譯錯誤:

錯誤 CS0246:找不到名為 'MyGrpcServices' 的類型或命名空間名稱 (是否遺漏 using 指示詞或組件參考?)

您可以透過下列方式解決此問題:

  1. 建立新的 .NET Core 類別庫專案。
  2. 在新專案中,新增參考以啟用.proto 檔案產生 C# 程式碼的功能:
  3. 在 WPF 應用程式中,新增新專案的參考。

WPF 應用程式可以使用新類別庫專案中 gRPC 產生的類型。

呼叫裝載在子目錄中的 gRPC 服務

警告

許多第三方 gRPC 工具不支援子目錄中裝載的服務。 請考慮尋找將 gRPC 裝載為根目錄的方法。

發出 gRPC 呼叫時,會忽略 gRPC 通道位址的路徑元件。 例如,在路由傳送服務的 gRPC 呼叫時,GrpcChannel.ForAddress("https://localhost:5001/ignored_path") 不會使用 ignored_path

由於 gRPC 具有標準化的規範位址結構,因此會忽略位址路徑。 gRPC 位址結合了套件、服務和方法名稱:https://localhost:5001/PackageName.ServiceName/MethodName

在某些情況下,應用程式需要包含具有 gRPC 呼叫的路徑。 例如,當 ASP.NET Core gRPC 應用程式裝載於 IIS 目錄,且該目錄必須包含在要求中時。 需要有路徑時,可以使用下列指定的自訂 SubdirectoryHandler 將其新增至 gRPC 呼叫:

/// <summary>
/// A delegating handler that adds a subdirectory to the URI of gRPC requests.
/// </summary>
public class SubdirectoryHandler : DelegatingHandler
{
    private readonly string _subdirectory;

    public SubdirectoryHandler(HttpMessageHandler innerHandler, string subdirectory)
        : base(innerHandler)
    {
        _subdirectory = subdirectory;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var old = request.RequestUri;

        var url = $"{old.Scheme}://{old.Host}:{old.Port}";
        url += $"{_subdirectory}{request.RequestUri.AbsolutePath}";
        request.RequestUri = new Uri(url, UriKind.Absolute);

        return base.SendAsync(request, cancellationToken);
    }
}

SubdirectoryHandler 會在建立 gRPC 通道時使用。

var handler = new SubdirectoryHandler(new HttpClientHandler(), "/MyApp");

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpHandler = handler });
var client = new Greet.GreeterClient(channel);

var reply = await client.SayHelloAsync(new HelloRequest { Name = ".NET" });

上述 程式碼:

  • 使用路徑 /MyApp 建立 SubdirectoryHandler
  • 設定通道以使用 SubdirectoryHandler
  • 使用 SayHelloAsync 呼叫 gRPC 服務。 gRPC 呼叫會傳送至 https://localhost:5001/MyApp/greet.Greeter/SayHello

或者,您可以使用 AddHttpMessageHandler 為用戶端處理站設定 SubdirectoryHandler

來自 HttpClient.Timeout 的 gRPC 呼叫逾時

HttpClient 會預設 100 秒的逾時。 如果 GrpcChannel 設定為使用 HttpClient,當長時間執行的 gRPC 串流呼叫未在逾時限制內完成時,這些呼叫就會取消。

System.OperationCanceledException: The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.

有幾種方法可以修正此錯誤。 第一個是將 HttpClient.Timeout 設定為較大的值。 Timeout.InfiniteTimeSpan 會停用逾時:

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var httpClient = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan };
var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpClient = httpClient });
var client = new Greeter.GreeterClient(channel);

或者,避免建立 HttpClient,改為設定 GrpcChannel.HttpHandler

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var channel = GrpcChannel.ForAddress("https://localhost:5001",
    new GrpcChannelOptions { HttpHandler = handler });
var client = new Greeter.GreeterClient(channel);