Skip to content

HTTP2 connections are closed after ~2 minutes of no activity #30387

@NxSoftware

Description

@NxSoftware

I've successfully been able to connect to APNS (Apple Push Notification Service) using HTTP2 and .NET Core 3.0 Preview 7.

According to their documentation they recommend keeping the connection open for basically as long as possible and that they treat "rapid connection and disconnection as a denial-of-service attack". See the section title "Best Practices for Managing Connections".

In order to cater for this I have tried to create a HttpClient that effectively lives for the duration of the app, expecting it to keep the connection open for that lifetime. Unfortunately it appears that the connection only stays open for approximately 2 minutes. Tested using the following terminal command on macOS Mojave 10.14.6 (18G84):

lsof -i -n | sort | grep "17\."

17. being the first octet of Apple's assigned IP block.

A new connection is opened when another request is sent using the same HttpClient but it seems like the connection shouldn't have been dropped in the first place. The only issue I can find that seems obviously related is https://github.com/dotnet/corefx/issues/31294.

Here is some code to reproduce the issue, implemented as a Hosted Service

public class Startup : IHostedService
{
    private HttpClient Client { get; set; }

    public static Task Main(string[] args)
    {
        return new HostBuilder()
            .ConfigureServices(s => s.AddHostedService<Startup>())
            .RunConsoleAsync();
    }

    public Startup()
    {
        var handler = new HttpClientHandler();
        handler.SslProtocols = SslProtocols.Tls12;
     
        Client = new HttpClient(handler, true);
        Client.BaseAddress = new Uri("https://api.push.apple.com/3/device/");
        Client.DefaultRequestVersion = new Version(2, 0);
        Client.DefaultRequestHeaders.Connection.Add("keep-alive");
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        var deviceToken = "ABC";

        Console.WriteLine($"{DateTime.Now} Sending...");
        var response = await Client.PostAsync(deviceToken, new StringContent(""));

        Console.WriteLine(response);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions