Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I enable http2 in C# Kestrel web server over plain http?

Tags:

c#

http

How do I (and is it possible) to enable http2 over plain http in the C# Kestrel web server? All Microsoft documentation indicates that https/TLS is required, but I have services that will be running behind a load-balancer or nginx and as such don't need a second layer of https. The official http2 spec indicates that https is not required.

like image 281
jjxtra Avatar asked Nov 03 '19 17:11

jjxtra


4 Answers

Scenarios to use unencrypted http2 are load balancers, proxies, etc.

You must do three things to use http2 over unencrypted channel.

Setup Kestrel to use http2 on your server:

builder.ConfigureWebHostDefaults((webBuilder) =>
{
    // this will keep your other end points settings such as --urls parameter
    webBuilder.ConfigureKestrel((options) =>
    {
        // trying to use Http1AndHttp2 causes http2 connections to fail with invalid protocol error
        // according to Microsoft dual http version mode not supported in unencrypted scenario: https://docs.microsoft.com/en-us/aspnet/core/grpc/troubleshoot?view=aspnetcore-3.0
        options.ConfigureEndpointDefaults(lo => lo.Protocols = HttpProtocols.Http2);
    });
});

For .net 5+, create your HttpClient instance, then create a message and specify the version:

var request = new HttpRequestMessage(HttpMethod.Get, uri)
{
    Version = HttpVersion.Version20,
    VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher
};

For .net core 3.1 and older, set a flag to enable http2 unencrypted. Then, when you create an HttpClient, specify the version:

AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
var client = new HttpClient { BaseAddress = new Uri(baseUrl), DefaultRequestVersion = new Version(2, 0) };

If you need to support both http1 and http2 on a completely unencrypted host, then you will need to listen on two ports, one for each http version. Then your load balancer or proxy would need to handle the http version and direct to the appropriate port.

You won't see http2 on your browser and will likely get a protocol error, so in those cases you can use an http1 protocol directive just for development environment. Not ideal, but it at least lets you test locally.

like image 143
jjxtra Avatar answered Sep 27 '22 23:09

jjxtra


Make sure you are using Microsoft.AspNetCore.WebHost instead of the generic Microsoft.Extensions.Hosting.Host for the CreateDefaultBuilder as you will run into an annoying OpenSslCryptographicException when running dotnet core 3 grpc containers on docker linux instances.

Also, if you are using health checks, be sure to expose it on a different port as illustrated in the below Program.cs snippet:

public static IWebHostBuilder CreateHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .ConfigureKestrel(options => {
            //grpc port for http2 connections
            options.ListenAnyIP(xxxx, listenOptions => {
                listenOptions.Protocols = HttpProtocols.Http2;
            });
            //add health check on a separate port instead of Http1AndHttp2 on same port
            options.ListenAnyIP(xxxx, listenOptions => {
                listenOptions.Protocols = HttpProtocols.Http1;
            });
        })
        .UseStartup<Startup>();
like image 43
mrogunlana Avatar answered Sep 27 '22 22:09

mrogunlana


The easiest way is to use configuration section for Kestrel in appSettings.json.

  "Kestrel": {
    "EndpointDefaults": {
      "Protocols": "Http2"
    }
  },

To turn of SSL, you may want to remove app.UseHsts(), app.UseHttpsRedirections() options from Statup.cs Configure method and expose only Http url.

You can also use Http1AndHttp2 to support both Http1 and Http2 together.

like image 27
Jins Peter Avatar answered Sep 27 '22 21:09

Jins Peter


In dotnet 5 AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); is no longer supported.

To allow prior knowledge http/2 you need to configure your HttpRequestMessage:

var request = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/something"))
            {
                Version = HttpVersion.Version20,
                VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher //This allows http/2 unencrypted,
            };

For reference: https://github.com/dotnet/runtime/issues/987

like image 32
Niklas Arbin Avatar answered Sep 27 '22 21:09

Niklas Arbin