diff --git a/OptimizelySDK/OptimizelySDK.csproj b/OptimizelySDK/OptimizelySDK.csproj index 1812a2ad..585d0b0a 100644 --- a/OptimizelySDK/OptimizelySDK.csproj +++ b/OptimizelySDK/OptimizelySDK.csproj @@ -173,6 +173,7 @@ + diff --git a/OptimizelySDK/Utils/HttpClientProvider.cs b/OptimizelySDK/Utils/HttpClientProvider.cs new file mode 100644 index 00000000..7507d76b --- /dev/null +++ b/OptimizelySDK/Utils/HttpClientProvider.cs @@ -0,0 +1,80 @@ +/* + * Copyright 2023 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Net.Http; +using System.Threading.Tasks; + +namespace OptimizelySDK.Utils +{ + /// + /// Provides a singleton instance of HttpClient to prevent socket exhaustion. + /// + public class HttpClientProvider + { + private static readonly object lockObject = new object(); + private Lazy _httpClient = new Lazy(CreateNewHttpClient); + + /// + /// Gets a thread-safe, singleton instance of HttpClient. + /// + /// HttpClient instance + public HttpClient GetClient() + { + if (IsDisposed(_httpClient.Value)) + { + lock (lockObject) + { + if (IsDisposed(_httpClient.Value)) + { + _httpClient = new Lazy(CreateNewHttpClient); + } + } + } + + return _httpClient.Value; + } + + /// + /// Creates a new HttpClient instance. + /// + /// HttpClient instance + private static HttpClient CreateNewHttpClient() + { + return new HttpClient(); + } + + /// + /// Checks if the HttpClient is disposed by attempting to access a property that + /// will throw an exception if the client is disposed. + /// + /// Client to check + /// True if the client is disposed, false otherwise. + private static bool IsDisposed(HttpClient httpClient) + { + try + { + _ = httpClient.BaseAddress; + } + catch (ObjectDisposedException) + { + return true; + } + + return false; + } + } +}