2

I am using asp.net core in one of my projects and I am making some https requests with a client certificate. To achieve this, I created a typed http client and injected it in my startup.cs like the following:

services.AddHttpClient<IClientService, ClientService>(c =>
            {
            }).ConfigurePrimaryHttpMessageHandler(() =>
            {
                var handler = new HttpClientHandler();
                handler.ClientCertificateOptions = ClientCertificateOption.Manual;
                handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls | SslProtocols.Tls11;
                handler.ClientCertificates.Add(clientCertificate);
                handler.ServerCertificateCustomValidationCallback = delegate { return true; };
                return handler;
            }
            );

My service is implemented like the following:

 public class ClientService : IClientService
    {
        public HttpClient _httpClient { get; private set; }
        private readonly string _remoteServiceBaseUrl;
        

        private readonly IOptions<AppSettings> _settings;


        public ClientService(HttpClient httpClient, IOptions<AppSettings> settings)
        {
            _httpClient = httpClient;
            _httpClient.Timeout = TimeSpan.FromSeconds(60);
            _settings = settings;
            _remoteServiceBaseUrl = $"{settings.Value.ClientUrl}";  /


        }

        async public Task<Model> GetInfo(string id)
        {
            var uri = ServiceAPI.API.GetOperation(_remoteServiceBaseUrl, id);
            var stream = await _httpClient.GetAsync(uri).Result.Content.ReadAsStreamAsync();
            var cbor = CBORObject.Read(stream);
            return JsonConvert.DeserializeObject<ModelDTO>(cbor.ToJSONString());
                
        }
    }

In my calling class, I am using this code:

public class CommandsApi 
    {
        IClientService _clientService;
       
        public CommandsApi( IclientService clientService)
           : base(applicationService, loggerFactory)
        {
            _clientService = clientService;
            _loggerfactory = loggerFactory;
        }
        public async Task<IActionResult> Post(V1.AddTransaction command)
        {

           
            var result = await _clientService.GetInfo(command.id);
        }
    }

It works just fine however after sending many requests I am receiving the foloowing error:

Cannot access a disposed object. Object name: 'SocketsHttpHandler'.
 at System.Net.Http.SocketsHttpHandler.CheckDisposed()
 at System.Net.Http.SocketsHttpHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
 at System.Net.Http.DelegatingHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
 at System.Net.Http.DiagnosticsHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
 at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
 at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
 at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)

I tried some solutions that I found in previous issues (asp.net core github )and stackoverflow but they did not work. Any idea ? Thank you

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
NEO
  • 61
  • 1
  • 6
  • This is a serious security bug: `handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls | SslProtocols.Tls11;` It tells .NET to *avoid* using TLS1.3 even if available and forces the use of TLS1.1 and the obsolete TLS1.0 even if they are disabled by default. You *shouldn't* set the `SslProtocols` property and let .NET Core use the best encryption provided by the OS – Panagiotis Kanavos Jun 29 '20 at 11:55
  • Yes this is just for dev – NEO Jun 29 '20 at 12:30
  • It's not needed at all then – Panagiotis Kanavos Jun 29 '20 at 12:51

3 Answers3

3

I suspect it's caused by resources not being disposed properly. There's an unnecessary call to .Result and you create a stream, but you don't dispose of it. If you use using statement, then the stream should be disposed. (you can always call stream.dispose() but I wouldn't recommend it).

var stream = await _httpClient.GetAsync(uri).Result.Content.ReadAsStreamAsync();

I've not run this, but consider:

public async Task<Model> GetInfo(string id)
{
    var uri = ServiceAPI.API.GetOperation(_remoteServiceBaseUrl, id);
    var response = await _httpClient.GetAsync(uri);

    using (var stream = await response.Content.ReadAsStreamAsync())
    {
        var cbor = CBORObject.Read(stream);
        return JsonConvert.DeserializeObject<ModelDTO>(cbor.ToJSONString());
    }
}
Greg
  • 4,468
  • 3
  • 16
  • 26
  • I tried your solution but I am still getting the same error. – NEO Jun 29 '20 at 12:26
  • I know that the problem is caused by the HttpClientHandler but I am still trying to solve it without success – NEO Jun 29 '20 at 12:29
  • On GetInfo you have public async the wrong way around, can you double check this? – Greg Jun 29 '20 at 12:36
  • The GetInfo is just a simplified example, my API is more complex, I just gave the example on how I am calling the service. – NEO Jun 29 '20 at 12:39
  • sorry about that. I've done some digging around, as a starting point, you might want to create a class that inherits from HttpClientHandler (and contains custom code) & inject the class in. See answer from: https://stackoverflow.com/questions/57875320/how-to-use-configureprimaryhttpmessagehandler-generic – Greg Jun 29 '20 at 12:50
  • My code is supposed to work correctly, I guess there is a bug in .net core. – NEO Jun 29 '20 at 12:57
  • I think you're right theres something odd in core. My guess is ConfigurePrimaryHttpMessageHandler is not disposing HttpClientHandler properly. – Greg Jun 29 '20 at 13:34
3

So after many tests I followed @Greg advice and implemented a class inheriting from HttpClientHandler and injected it like the following:

services.AddTransient<MyHttpClientHandler>();
services.AddHttpClient<IClientService, ClientService>().
                ConfigurePrimaryHttpMessageHandler<MyHttpClientHandler>();

this solved my problem. Thank you @Greg for the link How to use ConfigurePrimaryHttpMessageHandler generic

NEO
  • 61
  • 1
  • 6
0

This is not what the original question was for, but I stumbled onto this question while searching for a similar issue.

When adding an HttpMessageHandler to the HttpClient, make sure to always return a new HttpMessageHandler instance and not re-use the same HttpMessageHandler instance.

builder.Services
    .AddHttpClient<IClient>()
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        // Always return a new instance!!
        return new HttpClientHandler();
    });

The document of the ConfigurePrimaryHttpMessageHandler extension method states that

The configureHandler delegate should return a new instance of the message handler each time it is invoked.

See this github answer for more information https://github.com/dotnet/runtime/issues/27610#issuecomment-433101757

somethingRandom
  • 811
  • 11
  • 16