diff --git a/src/Flurl.Http/Configuration/FlurlClientBuilder.cs b/src/Flurl.Http/Configuration/FlurlClientBuilder.cs index a80a9605..7e01bb36 100644 --- a/src/Flurl.Http/Configuration/FlurlClientBuilder.cs +++ b/src/Flurl.Http/Configuration/FlurlClientBuilder.cs @@ -23,7 +23,11 @@ public interface IFlurlClientBuilder /// /// Configure the inner-most HttpMessageHandler associated with this IFlurlClient. /// +#if NETCOREAPP2_1_OR_GREATER + IFlurlClientBuilder ConfigureInnerHandler(Action configAction); +#else IFlurlClientBuilder ConfigureInnerHandler(Action configAction); +#endif /// /// Add a provided DelegatingHandler to the IFlurlClient. @@ -44,9 +48,13 @@ public class FlurlClientBuilder : IFlurlClientBuilder private readonly IFlurlClientFactory _factory; private readonly string _baseUrl; private readonly List> _addMiddleware = new(); - private readonly List> _configClient = new(); - private readonly List> _configHandler = new(); private readonly List> _configSettings = new(); + private readonly List> _configClient = new(); +#if NETCOREAPP2_1_OR_GREATER + private readonly HandlerBuilder _handlerBuilder = new(); +#else + private readonly HandlerBuilder _handlerBuilder = new(); +#endif /// /// Creates a new FlurlClientBuilder. @@ -80,24 +88,20 @@ public IFlurlClientBuilder ConfigureHttpClient(Action configAction) } /// +#if NETCOREAPP2_1_OR_GREATER + public IFlurlClientBuilder ConfigureInnerHandler(Action configAction) { +#else public IFlurlClientBuilder ConfigureInnerHandler(Action configAction) { - _configHandler.Add(configAction); +#endif + _handlerBuilder.Configs.Add(configAction); return this; } /// public IFlurlClient Build() { - var innerHandler = _factory.CreateInnerHandler(); - foreach (var config in _configHandler) { - if (innerHandler is HttpClientHandler hch) - config(hch); - else - throw new Exception("ConfigureInnerHandler can only be used when IFlurlClientFactory.CreateInnerHandler returns an instance of HttpClientFactory."); - } + var outerHandler = _handlerBuilder.Build(_factory); - HttpMessageHandler outerHandler = innerHandler; - foreach (var createMW in Enumerable.Reverse(_addMiddleware)) { - var middleware = createMW(); + foreach (var middleware in Enumerable.Reverse(_addMiddleware).Select(create => create())) { middleware.InnerHandler = outerHandler; outerHandler = middleware; } @@ -107,11 +111,27 @@ public IFlurlClient Build() { config(httpCli); var flurlCli = new FlurlClient(httpCli, _baseUrl); - foreach (var config in _configSettings) { + foreach (var config in _configSettings) config(flurlCli.Settings); - } return flurlCli; } + + // helper class to keep those compiler switches from getting too messy + private class HandlerBuilder where T : HttpMessageHandler + { + public List> Configs { get; } = new(); + + public HttpMessageHandler Build(IFlurlClientFactory fac) { + var handler = fac.CreateInnerHandler(); + foreach (var config in Configs) { + if (handler is T h) + config(h); + else + throw new Exception($"ConfigureInnerHandler expected an instance of {typeof(T).Name} but received an instance of {handler.GetType().Name}."); + } + return handler; + } + } } } diff --git a/src/Flurl.Http/Configuration/FlurlClientFactory.cs b/src/Flurl.Http/Configuration/FlurlClientFactory.cs index bae9faf7..2c27bac5 100644 --- a/src/Flurl.Http/Configuration/FlurlClientFactory.cs +++ b/src/Flurl.Http/Configuration/FlurlClientFactory.cs @@ -5,24 +5,23 @@ namespace Flurl.Http.Configuration { /// - /// Interface for methods used to build and cache IFlurlClient instances. + /// Interface for helper methods used to construct IFlurlClient instances. /// public interface IFlurlClientFactory { /// - /// Defines how HttpClient should be instantiated and configured by default. Do NOT attempt - /// to cache/reuse HttpClient instances here - that should be done at the FlurlClient level - /// via a custom FlurlClientFactory that gets registered globally. + /// Creates and configures a new HttpClient as needed when a new IFlurlClient instance is created. + /// Implementors should NOT attempt to cache or reuse HttpClient instances here - their lifetime is + /// bound one-to-one with an IFlurlClient, whose caching and reuse is managed by IFlurlClientCache. /// - /// The HttpMessageHandler used to construct the HttpClient. - /// + /// The HttpMessageHandler passed to the constructor of the HttpClient. HttpClient CreateHttpClient(HttpMessageHandler handler); /// - /// Defines how the HttpMessageHandler used by HttpClients that are created by - /// this factory should be instantiated and configured. + /// Creates and configures a new HttpMessageHandler as needed when a new IFlurlClient instance is created. + /// The default implementation creates an instance of SocketsHttpHandler for platforms that support it, + /// otherwise HttpClientHandler. /// - /// HttpMessageHandler CreateInnerHandler(); } @@ -42,38 +41,37 @@ public static class FlurlClientFactoryExtensions /// public class DefaultFlurlClientFactory : IFlurlClientFactory { - /// - /// Override in custom factory to customize the creation of HttpClient used in all Flurl HTTP calls - /// (except when one is passed explicitly to the FlurlClient constructor). In order not to lose - /// Flurl.Http functionality, it is recommended to call base.CreateClient and customize the result. - /// + /// public virtual HttpClient CreateHttpClient(HttpMessageHandler handler) { return new HttpClient(handler); } /// - /// Override in custom factory to customize the creation of the top-level HttpMessageHandler used in all - /// Flurl HTTP calls (except when an HttpClient is passed explicitly to the FlurlClient constructor). - /// In order not to lose Flurl.Http functionality, it is recommended to call base.CreateMessageHandler, and - /// either customize the returned HttpClientHandler, or set it as the InnerHandler of a DelegatingHandler. + /// Creates and configures a new HttpMessageHandler as needed when a new IFlurlClient instance is created. /// public virtual HttpMessageHandler CreateInnerHandler() { - var httpClientHandler = new HttpClientHandler(); + // Flurl has its own mechanisms for managing cookies and redirects, so we need to disable them in the inner handler. +#if NETCOREAPP2_1_OR_GREATER + var handler = new SocketsHttpHandler { + UseCookies = false, + AllowAutoRedirect = false, + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + }; +#else + var handler = new HttpClientHandler(); - // flurl has its own mechanisms for managing cookies and redirects + if (handler.SupportsRedirectConfiguration) + handler.AllowAutoRedirect = false; - try { httpClientHandler.UseCookies = false; } - catch (PlatformNotSupportedException) { } // look out for WASM platforms (#543) + // #266 + // deflate not working? see #474 + if (handler.SupportsAutomaticDecompression) + handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; - if (httpClientHandler.SupportsRedirectConfiguration) - httpClientHandler.AllowAutoRedirect = false; - - if (httpClientHandler.SupportsAutomaticDecompression) { - // #266 - // deflate not working? see #474 - httpClientHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; - } - return httpClientHandler; + try { handler.UseCookies = false; } + catch (PlatformNotSupportedException) { } // look out for WASM platforms (#543) +#endif + return handler; } } } diff --git a/test/Flurl.Test/Http/FlurlClientBuilderTests.cs b/test/Flurl.Test/Http/FlurlClientBuilderTests.cs index f30e37a6..527fdd42 100644 --- a/test/Flurl.Test/Http/FlurlClientBuilderTests.cs +++ b/test/Flurl.Test/Http/FlurlClientBuilderTests.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Text; using System.Threading; using System.Threading.Tasks; using Flurl.Http; @@ -52,6 +50,21 @@ public async Task can_add_middleware() { Assert.AreEqual("blocked by flurl!", resp); } + [Test] + public void inner_hanlder_is_SocketsHttpHandler_when_supported() { + var supported = typeof(HttpClientHandler).Assembly.DefinedTypes.Any(t => t.Name == "SocketsHttpHandler"); +#if NET + Assert.IsTrue(supported, "SocketsHttpHandler should be defined"); // sanity check (tests the test, basically) +#endif + HttpMessageHandler handler = null; + new FlurlClientBuilder() + .ConfigureInnerHandler(h => handler = h) + .Build(); + + var expected = supported ? "SocketsHttpHandler" : "HttpClientHandler"; + Assert.AreEqual(expected, handler?.GetType().Name); + } + class BlockingHandler : DelegatingHandler { private readonly string _msg;