简体   繁体   中英

HttpClient is slow when called in an https context in Asp.Net Core

I've got a server using Asp.Net Core, and I am having trouble with delays when using HTTPS. When only fetching a few images, each request takes around 20ms to process. However when I make 125 requests simultaneously they slow down to 30-80ms (acceptable) when using HTTP, and down to 130-850ms (unacceptable) when using HTTPS.

I've done some debugging and the slowdown occurs when my server makes the http call to the WMS server.

Adding a custom socket handler cut the delay down from 4000-6000ms to the current 130-850ms so that helped a lot, but the delay is still a bit much.

PS: The server will do a bit more than simply proxying the queries, so I can't simply use a dedicated proxy server for that role.

Here's a minimal example of the server code:

using System.Diagnostics.Tracing;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;

using var socketHandler = new SocketsHttpHandler()
{
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(10),
    PooledConnectionLifetime = TimeSpan.FromMinutes(10),
    MaxConnectionsPerServer = 1,
};

using var httpClient = new HttpClient(socketHandler);

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSingleton(httpClient);

var app = builder.Build();
app.MapControllers();
app.Run();


[ApiController]
[Route("[controller]")]
public class WMSController : ControllerBase
{
    private readonly HttpClient Client;
    private readonly string BaseUrl;

    public WMSController(HttpClient client)
    {
        Client = client;
        BaseUrl = "http://my-local-hostname.company.internal:10301/WMS";
    }

    [HttpGet(Name = "WMS")]
    public async Task<ActionResult> GetWms()
    {
        var query = HttpContext.Request.QueryString.Value;

        var stopwatch = new Stopwatch();
        stopwatch.Start();
        var response = await Client.GetAsync(BaseUrl + query); // This is the slow part.
        stopwatch.Stop();

        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - {stopwatch.ElapsedMilliseconds} - {HttpContext.Connection.Id}");
        stopwatch.Reset();
        var image = await response.Content.ReadAsByteArrayAsync();

        return new FileContentResult(image, "image/jpeg");
    }
}

There are a number of efficiencies you can make.

  • Use a global HttpClient and handler, so that requests to the same server can multiplex. This also prevents socket exhaustion.
static HttpClient httpClient = new HttpClient(new SocketsHttpHandler()
{
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(10),
    PooledConnectionLifetime = TimeSpan.FromMinutes(10),
});
  • Use HttpCompletionOption.ResponseHeadersRead so that only the headers are buffered, otherwise the full response has to be buffered.
    Note that the timings will now only be until the headers are received.
[HttpGet(Name = "WMS")]
public async Task<ActionResult> GetWms()
{
    var query = HttpContext.Request.QueryString.Value;

    var stopwatch = Stopwatch.StartNew();
    using var response = await Client.GetAsync(BaseUrl + query, HttpCompletionOption.ResponseHeadersRead);

    stopwatch.Stop();
    Console.WriteLine($"{DateTime.Now:HH:mm:ss} - {stopwatch.ElapsedMilliseconds} - {HttpContext.Connection.Id}");
    stopwatch.Reset();

    var image = await response.Content.ReadAsByteArrayAsync();
    return new FileContentResult(image, "image/jpeg");
}
  • A further improvement may only work if your use case supports it: feed the response as a stream directly to FileStreamResult
[HttpGet(Name = "WMS")]
public async Task<ActionResult> GetWms()
{
    var query = HttpContext.Request.QueryString.Value;

    var stopwatch = Stopwatch.StartNew();
    // NO using, otherwise stream gets disposed
    var response = await Client.GetAsync(BaseUrl + query, HttpCompletionOption.ResponseHeadersRead);

    stopwatch.Stop();
    Console.WriteLine($"{DateTime.Now:HH:mm:ss} - {stopwatch.ElapsedMilliseconds} - {HttpContext.Connection.Id}");
    stopwatch.Reset();

    var image = await response.Content.ReadAsStreamAsync();
    return new FileStreamResult(image, "image/jpeg");
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM