Nguon: Microsoft Learn · .NET 8.0

Xử lý sự cố gRPC trên .NET

Nguồn: Troubleshoot gRPC on .NET

Tài liệu này thảo luận về các vấn đề thường gặp khi phát triển ứng dụng gRPC trên .NET.

Không khớp cấu hình SSL/TLS giữa client và dịch vụ

Template gRPC và các mẫu sử dụng Transport Layer Security (TLS) để bảo mật dịch vụ gRPC theo mặc định. gRPC client cần sử dụng kết nối bảo mật để gọi thành công các dịch vụ gRPC được bảo mật.

Bạn có thể xác minh dịch vụ gRPC ASP.NET Core đang sử dụng TLS trong các log được ghi khi khởi động ứng dụng. Dịch vụ sẽ lắng nghe trên endpoint HTTPS:

output
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 client phải sử dụng https trong địa chỉ máy chủ để thực hiện cuộc gọi với kết nối bảo mật:

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

Tất cả các triển khai gRPC client đều hỗ trợ TLS. gRPC client từ các ngôn ngữ khác thường yêu cầu channel được cấu hình với SslCredentials. SslCredentials chỉ định chứng chỉ mà client sẽ sử dụng, và nó phải được sử dụng thay vì thông tin xác thực không bảo mật. Để biết ví dụ về cách cấu hình các triển khai gRPC client khác nhau để sử dụng TLS, xem gRPC Authentication.

Gọi dịch vụ gRPC với chứng chỉ không tin cậy/không hợp lệ

.NET gRPC client yêu cầu dịch vụ phải có chứng chỉ tin cậy. Thông báo lỗi sau được trả về khi gọi dịch vụ gRPC mà không có chứng chỉ tin cậy:

Unhandled exception. System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception. ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.

Bạn có thể thấy lỗi này nếu bạn đang kiểm thử ứng dụng cục bộ và chứng chỉ phát triển HTTPS của ASP.NET Core không được tin cậy. Để biết hướng dẫn khắc phục sự cố này, xem Trust the ASP.NET Core HTTPS development certificate on Windows and macOS.

Nếu bạn đang gọi dịch vụ gRPC trên máy khác và không thể tin cậy chứng chỉ, thì gRPC client có thể được cấu hình để bỏ qua chứng chỉ không hợp lệ. Code sau đây sử dụng HttpClientHandler.ServerCertificateCustomValidationCallback để cho phép các cuộc gọi mà không có chứng chỉ tin cậy:

csharp
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 client factory cho phép các cuộc gọi mà không có chứng chỉ tin cậy. Sử dụng phương thức mở rộng ConfigurePrimaryHttpMessageHandler để cấu hình handler trên client:

csharp
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;
    });

Cảnh báo: Chứng chỉ không tin cậy chỉ nên được sử dụng trong quá trình phát triển ứng dụng. Ứng dụng sản xuất phải luôn sử dụng chứng chỉ hợp lệ.

Gọi dịch vụ gRPC không bảo mật với .NET client

.NET gRPC client có thể gọi dịch vụ gRPC không bảo mật bằng cách chỉ định http trong địa chỉ máy chủ. Ví dụ: GrpcChannel.ForAddress("http://localhost:5000").

Có một số yêu cầu bổ sung để gọi dịch vụ gRPC không bảo mật tùy thuộc vào phiên bản .NET mà ứng dụng đang sử dụng:

Quan trọng: Dịch vụ gRPC không bảo mật phải được lưu trữ trên cổng chỉ HTTP/2. Để biết thêm thông tin, xem ASP.NET Core protocol negotiation.

Không thể khởi động ứng dụng gRPC ASP.NET Core trên macOS

Kestrel không hỗ trợ HTTP/2 với TLS trên macOS trước .NET 8. Template gRPC ASP.NET Core và các mẫu sử dụng TLS theo mặc định. Bạn sẽ thấy thông báo lỗi sau khi cố gắng khởi động máy chủ gRPC:

Unable to bind to https://localhost:5001 on the IPv4 loopback interface: 'HTTP/2 over TLS is not supported on macOS due to missing ALPN support.'.

Để khắc phục sự cố này trong .NET 7 hoặc cũ hơn, cấu hình Kestrel và gRPC client để sử dụng HTTP/2 mà không có TLS. Bạn chỉ nên làm điều này trong quá trình phát triển. Không sử dụng TLS sẽ dẫn đến các thông báo gRPC được gửi mà không có mã hóa. Để biết thêm thông tin, xem ASP.NET Core in .NET 7: Unable to start ASP.NET Core gRPC app on macOS.

Các asset gRPC C# không được tạo code từ các file .proto

Việc tạo code gRPC của các lớp cơ sở client và dịch vụ cụ thể yêu cầu các file protobuf và công cụ phải được tham chiếu từ dự án. Bạn phải bao gồm:

Để biết thêm thông tin về việc tạo asset gRPC C#, xem gRPC Services with C#.

Một ứng dụng web ASP.NET Core lưu trữ dịch vụ gRPC chỉ cần lớp cơ sở dịch vụ được tạo:

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

Một ứng dụng gRPC client thực hiện các cuộc gọi gRPC chỉ cần client cụ thể được tạo:

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

Dự án WPF không thể tạo asset gRPC C# từ các file .proto

Các dự án WPF có vấn đề đã biết ngăn việc tạo code gRPC hoạt động chính xác. Bất kỳ kiểu gRPC nào được tạo trong dự án WPF bằng cách tham chiếu Grpc.Tools và các file .proto sẽ tạo ra lỗi biên dịch khi được sử dụng:

error CS0246: The type or namespace name 'MyGrpcServices' could not be found (are you missing a using directive or an assembly reference?)

Bạn có thể khắc phục sự cố này bằng cách:

  1. Tạo dự án thư viện lớp .NET mới.
  2. Trong dự án mới, thêm tham chiếu để bật tạo code C# từ các file .proto:
  3. Thêm các tham chiếu gói sau:
  4. Grpc.Tools
  5. Grpc.Net.Client
  6. Google.Protobuf
  7. Thêm các file .proto vào nhóm item <Protobuf>.
  8. Trong ứng dụng WPF, thêm tham chiếu đến dự án mới.

Ứng dụng WPF có thể sử dụng các kiểu được tạo bởi gRPC từ dự án thư viện lớp mới.

Gọi dịch vụ gRPC được lưu trữ trong thư mục con

Cảnh báo: Nhiều công cụ gRPC của bên thứ ba không hỗ trợ dịch vụ được lưu trữ trong các thư mục con. Hãy cân nhắc tìm cách lưu trữ gRPC trong thư mục gốc.

Phần path của địa chỉ channel gRPC bị bỏ qua khi thực hiện các cuộc gọi gRPC. Ví dụ: GrpcChannel.ForAddress("https://localhost:5001/ignored_path") sẽ không sử dụng ignored_path khi định tuyến các cuộc gọi gRPC cho dịch vụ.

Đường dẫn địa chỉ bị bỏ qua vì gRPC có cấu trúc địa chỉ tiêu chuẩn, được quy định. Địa chỉ gRPC kết hợp tên gói, dịch vụ và phương thức: https://localhost:5001/PackageName.ServiceName/MethodName.

Có một số tình huống khi ứng dụng cần bao gồm path với các cuộc gọi gRPC. Ví dụ, khi ứng dụng gRPC ASP.NET Core được lưu trữ trong thư mục IIS và thư mục cần được bao gồm trong request. Khi path là cần thiết, nó có thể được thêm vào cuộc gọi gRPC bằng cách sử dụng SubdirectoryHandler tùy chỉnh được chỉ định bên dưới:

csharp
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 được sử dụng khi channel gRPC được tạo.

csharp
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" });

Code trên:

Ngoài ra, client factory có thể được cấu hình với SubdirectoryHandler bằng cách sử dụng AddHttpMessageHandler.

Cấu hình gRPC client để sử dụng HTTP/3

.NET gRPC client hỗ trợ HTTP/3 với .NET 6 trở lên. Nếu máy chủ gửi header response alt-svc cho client chỉ ra rằng máy chủ hỗ trợ HTTP/3, client sẽ tự động nâng cấp kết nối của nó lên HTTP/3. Để biết thêm thông tin, xem Use HTTP/3 with the ASP.NET Core Kestrel web server.

DelegatingHandler có thể được sử dụng để buộc gRPC client sử dụng HTTP/3. Việc buộc HTTP/3 tránh chi phí của việc nâng cấp request. Buộc HTTP/3 với code tương tự như sau:

csharp
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 được sử dụng khi channel gRPC được tạo. Code sau đây tạo một channel được cấu hình để sử dụng Http3Handler.

csharp
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" });

Ngoài ra, client factory có thể được cấu hình với Http3Handler bằng cách sử dụng AddHttpMessageHandler.

Xây dựng gRPC trên Alpine Linux

Gói Grpc.Tools tạo các kiểu .NET từ các file .proto bằng cách sử dụng binary gốc được đóng gói gọi là protoc. Cần thực hiện các bước bổ sung để xây dựng ứng dụng gRPC trên các nền tảng không được hỗ trợ bởi các binary gốc trong Grpc.Tools, chẳng hạn như Alpine Linux.

Tạo code trước

Một giải pháp là tạo code trước.

  1. Di chuyển các file .proto và tham chiếu gói Grpc.Tools sang dự án mới.
  2. Publish dự án dưới dạng gói NuGet và tải lên NuGet feed.
  3. Cập nhật ứng dụng để tham chiếu gói NuGet.

Với các bước trên, ứng dụng không còn yêu cầu Grpc.Tools để xây dựng vì code được tạo trước.

Tùy chỉnh binary gốc Grpc.Tools

Grpc.Tools hỗ trợ sử dụng các binary gốc tùy chỉnh. Tính năng này cho phép công cụ gRPC chạy trong các môi trường mà các binary gốc được đóng gói của nó không hỗ trợ.

Xây dựng hoặc mua các binary gốc protocgrpc_csharp_plugin và cấu hình Grpc.Tools để sử dụng chúng. Cấu hình binary gốc bằng cách đặt các biến môi trường sau:

Đối với Alpine Linux, có các gói do cộng đồng cung cấp cho trình biên dịch protocol buffers và plugin gRPC tại https://pkgs.alpinelinux.org/.

sh
# 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

Để biết thêm thông tin về việc sử dụng Grpc.Tools với các kiến trúc không được hỗ trợ, xem tài liệu tích hợp xây dựng gRPC.

Timeout của cuộc gọi gRPC từ HttpClient.Timeout

HttpClient được cấu hình với timeout mặc định là 100 giây. Nếu GrpcChannel được cấu hình để sử dụng HttpClient, các cuộc gọi streaming gRPC chạy dài sẽ bị hủy nếu chúng không hoàn thành trong giới hạn timeout.

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

Có một vài cách để khắc phục lỗi này. Cách đầu tiên là cấu hình HttpClient.Timeout sang giá trị lớn hơn. Timeout.InfiniteTimeSpan vô hiệu hóa timeout:

csharp
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);

Ngoài ra, tránh tạo HttpClient và đặt GrpcChannel.HttpHandler thay thế:

csharp
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);