ASP.NET Core and Http2, Http3, gRPC

This post is a quick lap around using http2 and http3 and gRPC endpoints with ASP.NET Core.

ASP.NET Core's Kestrel host on .NET 7 supports hosting endpoints with http1, http2 and http3 protocols. We can host gRPC endpoints with ASP.NET Core both using http2 and http3.

The two questions I would like to answer:

  • can we host http2 without SSL?

  • how to invoke an http2 endpoint without SSL?

  • how http3 REST performance compares with gRPC over h2?

HTTP

Endpoints with http1 and http2 protocols support connecting with and without SSL (TLS), http3 requires secure endpoints. While the http2 standard allows to send non-secure H2 requests, modern browsers typically do not allow this and fall back to http/1.1. In these cases, ASP.NET Core will return an error message An HTTP/1.x request was sent to an HTTP/2 only endpoint. in case the endpoint is http2 only.

To configure ASP.NET Core to use only http2 without TLS add the following json configuration to the appsettings.json file:

"Kestrel": {
    "Endpoints": {
      "http": {
        "Protocols": "Http2",
        "Url": "http://localhost:5001"
      }
    }
  }

Alternatively configure the endpoint options during application startup. To do that, add the following snippet to Program.cs:

builder.WebHost.UseKestrel(kestrelOptions =>
{
    kestrelOptions.ListenAnyIP(5001, listenOptions =>
    {
        listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http3;
    });
});

Finally, remove https redirection in the http pipeline:

app.UseHttpsRedirection();

To use only http3 without TLS add the following json configuration to the appsettings.json file.

"Kestrel": {
    "Endpoints": {
      "http": {
        "Protocols": "Http3",
        "Url": "https://localhost:5001"
      }
    }
  }

HttpClient

To make http2 and http3 requests with HttpClient set the DefaultVersionPolicy and DefaultRequestVersion accordingly, then use one of the GetAsync, PutAsync, PostAsync, etc. methods fire the HTTP request.

HttpClient client = new HttpClient();
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact;
client.DefaultRequestVersion = System.Net.HttpVersion.Version30;
var result = await client.GetAsync("https://localhost:5001/person/1", HttpCompletionOption.ResponseHeadersRead);

However, when using the SendAsync method with an instance of HttpRequestMessage make sure to set the Version and VersionPolicy properties on the request message itself:

await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://localhost:5001/person/1") { Version = HttpVersion.Version30, VersionPolicy = HttpVersionPolicy.RequestVersionExact }, HttpCompletionOption.ResponseHeadersRead);

Without setting the values, the DefaultVersionPolicy and DefaultRequestVersion values set on the HttpClient will be overwritten by the defaults of HttpRequestMessage.

gRPC

A http3 request-response body is typically a UTF-8 encoded json payload. gRPC uses Protocol Buffers data format which is significantly more efficient. I was curious how much is "more efficient" it is. As this depends on the application and endpoint in question, I set up a sample request response pair, and implemented an endpoint in REST (with H3) and gRPC (with H2). For brevity, the request-response pair is omitted from this post, but it was a typical DTO representing a Person and its Address.

Implemented two client applications to compare the performance: one using HttpClient and one with gRPC client. Both, firing 10000 requests from 100 parallel threads. In my tests the gRPC service yields ~30% better performance to the REST endpoint. Most of the gains come from the efficiency of encoding and decoding proto messages. To reveal how efficient a proto message is, I compared the size of the response messages: the sample message was 171 bytes with gRPC, and 381 bytes with the json encoding. These number are completely specific to the sample data processed and by these endpoints.

I ask the kind reader not to generalize from these results. To make a well-informed decision from performance point of view over one or the other technology, care measurements must be made that are tailored to the characteristics of the application in question.