YetAnotherHttpHandler

repository·main·Indexed 19 days ago

https://github.com/cysharp/yetanotherhttphandler

A high-performance HTTP/2 handler for Unity and .NET Standard built on Rust's hyper and rustls libraries. It serves as a drop-in replacement for HttpClient handlers and enables HTTP/2 and gRPC (via grpc-dotnet) in environments lacking native support. Features include support for HTTP/2 over cleartext (h2c), custom root certificates, Unix Domain Sockets (UDS) for local IPC, and configurable worker threads via the tokio runtime.

Tokens
5.4K
Snippets
17
Records
18
Agent score
68%

What's inside YetAnotherHttpHandler

  1. Install YetAnotherHttpHandler in Unity

    main

    You can install YetAnotherHttpHandler in Unity using one of three methods. Note that the library requires System.IO.Pipelines and System.Runtime.CompilerServices.Unsafe (netstandard2.1) as dependencies.

    Method 1: NuGetForUnity + GitHub

    1. Install NuGetForUnity.
    2. Install System.IO.Pipelines and System.Runtime.CompilerServices.Unsafe via NuGetForUnity.
    3. In Unity Package Manager, select Add package from git URL... and use: https://github.com/Cysharp/YetAnotherHttpHandler.git?path=src/YetAnotherHttpHandler#{Version} (Replace {Version} with your desired version, e.g., 1.11.5).

    Method 2: UnityNuGet scope registry

    1. Add the UnityNuget scope registry.
    2. In Unity Package Manager, add the dependency package via git URL: https://github.com/Cysharp/YetAnotherHttpHandler.git?path=src/YetAnotherHttpHandler.Dependencies#{Version}
    3. Add the main package via git URL: https://github.com/Cysharp/YetAnotherHttpHandler.git?path=src/YetAnotherHttpHandler#{Version}

    Method 3: Manual Installation (GitHub/Releases)

    1. Download the Cysharp.Net.Http.YetAnotherHttpHandler.Dependencies.unitypackage from the Releases page.
    2. Install the .unitypackage into your project.
    3. Add the main package via Package Manager using the git URL: https://github.com/Cysharp/YetAnotherHttpHandler.git?path=src/YetAnotherHttpHandler#{Version}
    https://github.com/Cysharp/YetAnotherHttpHandler.git?path=src/YetAnotherHttpHandler#1.11.5
  2. Use Unix Domain Sockets (UDS) as the HTTP transport layer

    main

    For local Inter-Process Communication (IPC) via gRPC, you can use Unix Domain Sockets instead of TCP.

    Configuration:

    1. Set the UnixDomainSocketPath property to the path of the socket.
    2. Set Http2Only = true (required for UDS).
    3. When using grpc-dotnet, pass an http://localhost URI to GrpcChannel.ForAddress. The handler will internally redirect requests to the UDS path.

    Important Notes:

    • HTTPS is not supported over UDS. All HTTPS-related configuration properties are ignored.
    • If using Kestrel on the server side, you must set KestrelServerOptions.AllowAlternateSchemes = true.
    using var handler = new YetAnotherHttpHandler() { Http2Only = true, UnixDomainSocketPath = "/tmp/example.sock" };
    using var channel = GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions() { HttpHandler = handler });
  3. Use YetAnotherHttpHandler with gRPC (grpc-dotnet)

    main

    To use the grpc-dotnet library (specifically Grpc.Net.Client) with YetAnotherHttpHandler, you must first ensure the following dependencies are installed:

    • Grpc.Core.Api
    • Grpc.Net.Client
    • Grpc.Net.Common
    • Microsoft.Extensions.Logging.Abstractions
    • System.Diagnostics.DiagnosticSource

    Once dependencies are met, pass the handler to the GrpcChannelOptions.HttpHandler property.

    using Cysharp.Net.Http;
    
    using var handler = new YetAnotherHttpHandler();
    using var channel = GrpcChannel.ForAddress("https://api.example.com", new GrpcChannelOptions() { HttpHandler = handler });
    var greeter = new GreeterClient(channel);
    
    var result = await greeter.SayHelloAsync(new HelloRequest { Name = "Alice" });
    
    // Alternatively, if you want the channel to manage the handler disposal:
    using var channel = GrpcChannel.ForAddress("https://api.example.com", new GrpcChannelOptions() { HttpHandler = new YetAnotherHttpHandler(), DisposeHttpClient = true });
    var greeter = new GreeterClient(channel);
  4. Configure a gRPC client with GrpcChannel

    main

    gRPC clients are concrete types generated from .proto files. To use them, you must first create a GrpcChannel using GrpcChannel.ForAddress, which represents a long-lived connection to the service. You then pass this channel into the constructor of your generated client class.

    You can configure the channel using GrpcChannelOptions to specify settings like the HttpClient, maximum message sizes, and logging.

    var channel = GrpcChannel.ForAddress("https://localhost:5001");
    var client = new Greet.GreeterClient(channel);
  5. Troubleshoot System.DllNotFoundException on Windows

    main

    If you encounter System.DllNotFoundException: Unable to load DLL 'Cysharp.Net.Http.YetAnotherHttpHandler.Native', it is likely because the Visual C++ Redistributable Package (vcruntime140.dll) is missing.

    Solution: Download and install the latest supported VC++ Redistributable from the Microsoft website.

  6. Make a Client streaming gRPC call

    main

    In a client streaming call, the client sends a stream of messages to the server and waits for a single response.

    Workflow:

    1. Initiate the call.
    2. Use RequestStream.WriteAsync to send multiple messages.
    3. Call RequestStream.CompleteAsync() to notify the server that the client has finished sending.
    4. Await the call object itself to receive the final response message.
    var client = new Counter.CounterClient(channel);
    using var call = client.AccumulateCount();
    
    for (var i = 0; i < 3; i++)
    {
        await call.RequestStream.WriteAsync(new CounterRequest { Count = 1 });
    }
    await call.RequestStream.CompleteAsync();
    
    var response = await call;
    Console.WriteLine($"Count: {response.Count}");
  7. Make a Bi-directional streaming gRPC call

    main

    Bi-directional streaming allows both the client and server to send a stream of messages independently.

    To complete a bi-directional call gracefully:

    1. Start the call.
    2. Start a background task to read incoming messages from ResponseStream.ReadAllAsync().
    3. Send messages using RequestStream.WriteAsync.
    4. Notify the server that the client is done by calling RequestStream.CompleteAsync().
    5. Wait for the background reading task to finish to ensure all server messages are processed.
    var client = new Echo.EchoClient(channel);
    using var call = client.Echo();
    
    // 1. Start background task to receive messages
    var readTask = Task.Run(async () =>
    {
        await foreach (var response in call.ResponseStream.ReadAllAsync())
        {
            Console.WriteLine(response.Message);
        }
    });
    
    // 2. Send messages
    while (true)
    {
        var result = Console.ReadLine();
        if (string.IsNullOrEmpty(result)) break;
        await call.RequestStream.WriteAsync(new EchoMessage { Message = result });
    }
    
    // 3. Graceful completion
    await call.RequestStream.CompleteAsync();
    await readTask;
  8. Make a Unary gRPC call

    main

    A unary call is a simple request-response pattern. For every unary method defined in a .proto file, the generated client provides two methods:

    1. MethodNameAsync: An asynchronous method that can be awaited (recommended).
    2. MethodName: A blocking method that blocks the thread until the response is received (do not use in asynchronous code).

    Example of an asynchronous unary call:

    var client = new Greet.GreeterClient(channel);
    var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });
    
    Console.WriteLine("Greeting: " + response.Message);
  9. Make a Server streaming gRPC call

    main

    In a server streaming call, the client sends one request and the server responds with a stream of messages. You can consume the stream using ResponseStream.MoveNext() in a loop, or more idiomatically using await foreach with the ReadAllAsync() extension method (available in C# 8+).

    Note: Ensure you wrap the call in a using block to properly dispose of the call object.

    var client = new Greet.GreeterClient(channel);
    using var call = client.SayHellos(new HelloRequest { Name = "World" });
    
    await foreach (var response in call.ResponseStream.ReadAllAsync())
    {
        Console.WriteLine("Greeting: " + response.Message);
    }
  10. Use YetAnotherHttpHandler with HttpClient

    main

    YetAnotherHttpHandler is a drop-in replacement for HttpClient handlers. To enable HTTP/2 support, instantiate YetAnotherHttpHandler and pass it to the HttpClient constructor.

    YetAnotherHttpHandler and HttpClient can be shared across multiple threads or requests. However, because the handler does not support connection control by the number of streams, you must create separate handler instances if you need to explicitly manage different connections.

    using Cysharp.Net.Http;
    
    using var handler = new YetAnotherHttpHandler();
    var httpClient = new HttpClient(handler);
    
    var result = await httpClient.GetStringAsync("https://www.example.com");
  11. Configure custom root certificates

    main

    By default, YetAnotherHttpHandler uses Mozilla's root certificates (derived from webpki). To use self-signed certificates or organization-issued certificates, provide the root certificates in PEM format via the RootCertificates property.

    var rootCerts = @"
    -----BEGIN CERTIFICATE-----
    ...
    -----END CERTIFICATE-----
    ";
    using var handler = new YetAnotherHttpHandler() { RootCertificates = rootCerts };