From bec56047204afebb6dcddd90f90512027dd34cd3 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Sun, 31 Dec 2017 00:04:43 +0000 Subject: [PATCH] Switch to use a single HTTPClient rather than a new one every request !dev --- src/Ombi.Api/Api.cs | 125 ++++++++---------- src/Ombi.Api/IOmbiHttpClient.cs | 10 ++ src/Ombi.Api/OmbiHttpClient.cs | 86 ++++++++++++ src/Ombi.DependencyInjection/IocExtensions.cs | 1 + 4 files changed, 150 insertions(+), 72 deletions(-) create mode 100644 src/Ombi.Api/IOmbiHttpClient.cs create mode 100644 src/Ombi.Api/OmbiHttpClient.cs diff --git a/src/Ombi.Api/Api.cs b/src/Ombi.Api/Api.cs index 15897c2a1..25e36666f 100644 --- a/src/Ombi.Api/Api.cs +++ b/src/Ombi.Api/Api.cs @@ -16,29 +16,18 @@ namespace Ombi.Api { public class Api : IApi { - public Api(ILogger log, ISettingsService s, ICacheService cache) + public Api(ILogger log, ISettingsService s, ICacheService cache, IOmbiHttpClient client) { Logger = log; _settings = s; _cache = cache; + _client = client; } private ILogger Logger { get; } private readonly ISettingsService _settings; private readonly ICacheService _cache; - - private async Task GetHandler() - { - var settings = await _cache.GetOrAdd(CacheKeys.OmbiSettings, async () => await _settings.GetSettingsAsync(), DateTime.Now.AddHours(1)); - if (settings.IgnoreCertificateErrors) - { - return new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (message, certificate2, arg3, arg4) => true, - }; - } - return new HttpClientHandler(); - } + private readonly IOmbiHttpClient _client; private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings { @@ -47,89 +36,82 @@ namespace Ombi.Api public async Task Request(Request request) { - using(var handler = await GetHandler()) - using (var httpClient = new HttpClient(handler)) + using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri)) { - using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri)) + // Add the Json Body + if (request.JsonBody != null) { - // Add the Json Body - if (request.JsonBody != null) + httpRequestMessage.Content = new JsonContent(request.JsonBody); + httpRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); // Emby connect fails if we have the charset in the header + } + + // Add headers + foreach (var header in request.Headers) + { + httpRequestMessage.Headers.Add(header.Key, header.Value); + + } + using (var httpResponseMessage = await _client.SendAsync(httpRequestMessage)) + { + if (!httpResponseMessage.IsSuccessStatusCode) { - httpRequestMessage.Content = new JsonContent(request.JsonBody); - httpRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); // Emby connect fails if we have the charset in the header + Logger.LogError(LoggingEvents.Api, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}"); } - - // Add headers - foreach (var header in request.Headers) + // do something with the response + var data = httpResponseMessage.Content; + var receivedString = await data.ReadAsStringAsync(); + if (request.ContentType == ContentType.Json) { - httpRequestMessage.Headers.Add(header.Key, header.Value); - + request.OnBeforeDeserialization?.Invoke(receivedString); + return JsonConvert.DeserializeObject(receivedString, Settings); } - using (var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage)) + else { - if (!httpResponseMessage.IsSuccessStatusCode) - { - Logger.LogError(LoggingEvents.Api, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}"); - } - // do something with the response - var data = httpResponseMessage.Content; - var receivedString = await data.ReadAsStringAsync(); - if (request.ContentType == ContentType.Json) - { - request.OnBeforeDeserialization?.Invoke(receivedString); - return JsonConvert.DeserializeObject(receivedString, Settings); - } - else - { - // XML - XmlSerializer serializer = new XmlSerializer(typeof(T)); - StringReader reader = new StringReader(receivedString); - var value = (T)serializer.Deserialize(reader); - return value; - } + // XML + XmlSerializer serializer = new XmlSerializer(typeof(T)); + StringReader reader = new StringReader(receivedString); + var value = (T)serializer.Deserialize(reader); + return value; } } } + } public async Task RequestContent(Request request) { - using (var httpClient = new HttpClient(await GetHandler())) + using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri)) { - using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri)) + // Add the Json Body + if (request.JsonBody != null) { - // Add the Json Body - if (request.JsonBody != null) - { - httpRequestMessage.Content = new JsonContent(request.JsonBody); - } + httpRequestMessage.Content = new JsonContent(request.JsonBody); + } - // Add headers - foreach (var header in request.Headers) - { - httpRequestMessage.Headers.Add(header.Key, header.Value); + // Add headers + foreach (var header in request.Headers) + { + httpRequestMessage.Headers.Add(header.Key, header.Value); - } - using (var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage)) + } + using (var httpResponseMessage = await _client.SendAsync(httpRequestMessage)) + { + if (!httpResponseMessage.IsSuccessStatusCode) { - if (!httpResponseMessage.IsSuccessStatusCode) - { - Logger.LogError(LoggingEvents.Api, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}"); - } - // do something with the response - var data = httpResponseMessage.Content; + Logger.LogError(LoggingEvents.Api, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}"); + } + // do something with the response + var data = httpResponseMessage.Content; - return await data.ReadAsStringAsync(); - } + return await data.ReadAsStringAsync(); } } + } public async Task Request(Request request) { - using (var httpClient = new HttpClient(await GetHandler())) - { using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri)) { // Add the Json Body @@ -144,7 +126,7 @@ namespace Ombi.Api httpRequestMessage.Headers.Add(header.Key, header.Value); } - using (var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage)) + using (var httpResponseMessage = await _client.SendAsync(httpRequestMessage)) { if (!httpResponseMessage.IsSuccessStatusCode) { @@ -152,7 +134,6 @@ namespace Ombi.Api } } } - } } } } diff --git a/src/Ombi.Api/IOmbiHttpClient.cs b/src/Ombi.Api/IOmbiHttpClient.cs new file mode 100644 index 000000000..6c2e22f7c --- /dev/null +++ b/src/Ombi.Api/IOmbiHttpClient.cs @@ -0,0 +1,10 @@ +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ombi.Api +{ + public interface IOmbiHttpClient + { + Task SendAsync(HttpRequestMessage request); + } +} \ No newline at end of file diff --git a/src/Ombi.Api/OmbiHttpClient.cs b/src/Ombi.Api/OmbiHttpClient.cs new file mode 100644 index 000000000..ba21ddaa3 --- /dev/null +++ b/src/Ombi.Api/OmbiHttpClient.cs @@ -0,0 +1,86 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: OmbiHttpClient.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Settings.Settings.Models; + +namespace Ombi.Api +{ + /// + /// The purpose of this class is simple, keep one instance of the HttpClient in play. + /// There are many articles related to when using multiple HttpClient's keeping the socket in a WAIT state + /// https://blogs.msdn.microsoft.com/alazarev/2017/12/29/disposable-finalizers-and-httpclient/ + /// https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ + /// + public class OmbiHttpClient : IOmbiHttpClient + { + public OmbiHttpClient(ICacheService cache, ISettingsService s) + { + _cache = cache; + _settings = s; + } + + private static HttpClient _client; + private static HttpMessageHandler _handler; + + private readonly ICacheService _cache; + private readonly ISettingsService _settings; + + + public async Task SendAsync(HttpRequestMessage request) + { + if (_client == null) + { + if (_handler == null) + { + // Get the handler + _handler = await GetHandler(); + } + _client = new HttpClient(_handler); + } + + return await _client.SendAsync(request); + } + + private async Task GetHandler() + { + var settings = await _cache.GetOrAdd(CacheKeys.OmbiSettings, async () => await _settings.GetSettingsAsync(), DateTime.Now.AddHours(1)); + if (settings.IgnoreCertificateErrors) + { + return new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (message, certificate2, arg3, arg4) => true, + }; + } + return new HttpClientHandler(); + } + } +} \ No newline at end of file diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index f75a2530c..77350db40 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -86,6 +86,7 @@ namespace Ombi.DependencyInjection public static void RegisterApi(this IServiceCollection services) { services.AddTransient(); + services.AddSingleton(); // https://blogs.msdn.microsoft.com/alazarev/2017/12/29/disposable-finalizers-and-httpclient/ services.AddTransient(); services.AddTransient(); services.AddTransient();