-
-
Notifications
You must be signed in to change notification settings - Fork 390
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #771 from tmenier/dev
Flurl.Http 4.0-pre5
- Loading branch information
Showing
25 changed files
with
663 additions
and
524 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net.Http; | ||
|
||
namespace Flurl.Http.Configuration | ||
{ | ||
/// <summary> | ||
/// A builder for configuring IFlurlClient instances. | ||
/// </summary> | ||
public interface IFlurlClientBuilder | ||
{ | ||
/// <summary> | ||
/// Configure the IFlurlClient's Settings. | ||
/// </summary> | ||
IFlurlClientBuilder WithSettings(Action<FlurlHttpSettings> configure); | ||
|
||
/// <summary> | ||
/// Configure the HttpClient wrapped by this IFlurlClient. | ||
/// </summary> | ||
IFlurlClientBuilder ConfigureHttpClient(Action<HttpClient> configure); | ||
|
||
/// <summary> | ||
/// Configure the inner-most HttpMessageHandler associated with this IFlurlClient. | ||
/// </summary> | ||
#if NETCOREAPP2_1_OR_GREATER | ||
IFlurlClientBuilder ConfigureInnerHandler(Action<SocketsHttpHandler> configure); | ||
#else | ||
IFlurlClientBuilder ConfigureInnerHandler(Action<HttpClientHandler> configure); | ||
#endif | ||
|
||
/// <summary> | ||
/// Add a provided DelegatingHandler to the IFlurlClient. | ||
/// </summary> | ||
IFlurlClientBuilder AddMiddleware(Func<DelegatingHandler> create); | ||
|
||
/// <summary> | ||
/// Builds an instance of IFlurlClient based on configurations specified. | ||
/// </summary> | ||
IFlurlClient Build(); | ||
} | ||
|
||
/// <summary> | ||
/// Default implementation of IFlurlClientBuilder. | ||
/// </summary> | ||
public class FlurlClientBuilder : IFlurlClientBuilder | ||
{ | ||
private readonly IFlurlClientFactory _factory; | ||
private readonly string _baseUrl; | ||
private readonly List<Func<DelegatingHandler>> _addMiddleware = new(); | ||
private readonly List<Action<FlurlHttpSettings>> _configSettings = new(); | ||
private readonly List<Action<HttpClient>> _configClient = new(); | ||
#if NETCOREAPP2_1_OR_GREATER | ||
private readonly HandlerBuilder<SocketsHttpHandler> _handlerBuilder = new(); | ||
#else | ||
private readonly HandlerBuilder<HttpClientHandler> _handlerBuilder = new(); | ||
#endif | ||
|
||
/// <summary> | ||
/// Creates a new FlurlClientBuilder. | ||
/// </summary> | ||
public FlurlClientBuilder(string baseUrl = null) : this(new DefaultFlurlClientFactory(), baseUrl) { } | ||
|
||
/// <summary> | ||
/// Creates a new FlurlClientBuilder. | ||
/// </summary> | ||
internal FlurlClientBuilder(IFlurlClientFactory factory, string baseUrl) { | ||
_factory = factory; | ||
_baseUrl = baseUrl; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public IFlurlClientBuilder WithSettings(Action<FlurlHttpSettings> configure) { | ||
_configSettings.Add(configure); | ||
return this; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public IFlurlClientBuilder AddMiddleware(Func<DelegatingHandler> create) { | ||
_addMiddleware.Add(create); | ||
return this; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public IFlurlClientBuilder ConfigureHttpClient(Action<HttpClient> configure) { | ||
_configClient.Add(configure); | ||
return this; | ||
} | ||
|
||
/// <inheritdoc /> | ||
#if NETCOREAPP2_1_OR_GREATER | ||
public IFlurlClientBuilder ConfigureInnerHandler(Action<SocketsHttpHandler> configure) { | ||
#else | ||
public IFlurlClientBuilder ConfigureInnerHandler(Action<HttpClientHandler> configure) { | ||
#endif | ||
_handlerBuilder.Configs.Add(configure); | ||
return this; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public IFlurlClient Build() { | ||
var outerHandler = _handlerBuilder.Build(_factory); | ||
|
||
foreach (var middleware in Enumerable.Reverse(_addMiddleware).Select(create => create())) { | ||
middleware.InnerHandler = outerHandler; | ||
outerHandler = middleware; | ||
} | ||
|
||
var httpCli = _factory.CreateHttpClient(outerHandler); | ||
foreach (var config in _configClient) | ||
config(httpCli); | ||
|
||
var flurlCli = new FlurlClient(httpCli, _baseUrl); | ||
foreach (var config in _configSettings) | ||
config(flurlCli.Settings); | ||
|
||
return flurlCli; | ||
} | ||
|
||
// helper class to keep those compiler switches from getting too messy | ||
private class HandlerBuilder<T> where T : HttpMessageHandler | ||
{ | ||
public List<Action<T>> 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; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
using System; | ||
using System.Collections.Concurrent; | ||
|
||
namespace Flurl.Http.Configuration | ||
{ | ||
/// <summary> | ||
/// Interface for a cache of IFlurlClient instances. | ||
/// </summary> | ||
public interface IFlurlClientCache | ||
{ | ||
/// <summary> | ||
/// Adds and returns a new IFlurlClient. Call once per client at startup to register and configure a named client. | ||
/// </summary> | ||
/// <param name="name">Name of the IFlurlClient. Serves as a cache key. Subsequent calls to Get will return this client.</param> | ||
/// <param name="baseUrl">Optional. The base URL associated with the new client.</param> | ||
/// <returns></returns> | ||
IFlurlClientBuilder Add(string name, string baseUrl = null); | ||
|
||
/// <summary> | ||
/// Gets a named IFlurlClient, creating one if it doesn't exist or has been disposed. | ||
/// </summary> | ||
/// <param name="name">The client name.</param> | ||
/// <returns></returns> | ||
IFlurlClient Get(string name); | ||
|
||
/// <summary> | ||
/// Configuration logic that gets executed for every new IFlurlClient added this case. Good place for things like default | ||
/// settings. Executes before client-specific builder logic. | ||
/// </summary> | ||
IFlurlClientCache ConfigureAll(Action<IFlurlClientBuilder> configure); | ||
|
||
/// <summary> | ||
/// Removes a named client from this cache. | ||
/// </summary> | ||
void Remove(string name); | ||
|
||
/// <summary> | ||
/// Disposes and removes all cached IFlurlClient instances. | ||
/// </summary> | ||
void Clear(); | ||
} | ||
|
||
/// <summary> | ||
/// Default implementation of IFlurlClientCache. | ||
/// </summary> | ||
public class FlurlClientCache : IFlurlClientCache { | ||
private readonly ConcurrentDictionary<string, Lazy<IFlurlClient>> _clients = new(); | ||
private readonly IFlurlClientFactory _factory = new DefaultFlurlClientFactory(); | ||
private Action<IFlurlClientBuilder> _configureAll; | ||
|
||
/// <inheritdoc /> | ||
public IFlurlClientBuilder Add(string name, string baseUrl = null) { | ||
if (_clients.ContainsKey(name)) | ||
throw new ArgumentException($"A client named '{name}' was already registered with this factory. AddClient should be called just once per client at startup."); | ||
|
||
var builder = new FlurlClientBuilder(_factory, baseUrl); | ||
_clients[name] = CreateLazyInstance(builder); | ||
return builder; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public virtual IFlurlClient Get(string name) { | ||
if (name == null) | ||
throw new ArgumentNullException(nameof(name)); | ||
|
||
Lazy<IFlurlClient> Create() => CreateLazyInstance(new FlurlClientBuilder(_factory, null)); | ||
return _clients.AddOrUpdate(name, _ => Create(), (_, existing) => existing.Value.IsDisposed ? Create() : existing).Value; | ||
} | ||
|
||
private Lazy<IFlurlClient> CreateLazyInstance(FlurlClientBuilder builder) { | ||
_configureAll?.Invoke(builder); | ||
return new Lazy<IFlurlClient>(builder.Build); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public IFlurlClientCache ConfigureAll(Action<IFlurlClientBuilder> configure) { | ||
_configureAll = configure; | ||
return this; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public void Remove(string name) { | ||
if (_clients.TryRemove(name, out var cli) && cli.IsValueCreated && !cli.Value.IsDisposed) | ||
cli.Value.Dispose(); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public void Clear() { | ||
// Remove takes care of disposing too, which is why we don't simply call _clients.Clear | ||
foreach (var key in _clients.Keys) | ||
Remove(key); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
using System; | ||
using System.Net; | ||
using System.Net.Http; | ||
|
||
namespace Flurl.Http.Configuration | ||
{ | ||
/// <summary> | ||
/// Interface for helper methods used to construct IFlurlClient instances. | ||
/// </summary> | ||
public interface IFlurlClientFactory | ||
{ | ||
/// <summary> | ||
/// 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. | ||
/// </summary> | ||
/// <param name="handler">The HttpMessageHandler passed to the constructor of the HttpClient.</param> | ||
HttpClient CreateHttpClient(HttpMessageHandler handler); | ||
|
||
/// <summary> | ||
/// 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. | ||
/// </summary> | ||
HttpMessageHandler CreateInnerHandler(); | ||
} | ||
|
||
/// <summary> | ||
/// Extension methods on IFlurlClientFactory | ||
/// </summary> | ||
public static class FlurlClientFactoryExtensions | ||
{ | ||
/// <summary> | ||
/// Creates an HttpClient with the HttpMessageHandler returned from this factory's CreateInnerHandler method. | ||
/// </summary> | ||
public static HttpClient CreateHttpClient(this IFlurlClientFactory fac) => fac.CreateHttpClient(fac.CreateInnerHandler()); | ||
} | ||
|
||
/// <summary> | ||
/// Default implementation of IFlurlClientFactory, used to build and cache IFlurlClient instances. | ||
/// </summary> | ||
public class DefaultFlurlClientFactory : IFlurlClientFactory | ||
{ | ||
/// <inheritdoc /> | ||
public virtual HttpClient CreateHttpClient(HttpMessageHandler handler) { | ||
return new HttpClient(handler); | ||
} | ||
|
||
/// <summary> | ||
/// Creates and configures a new HttpMessageHandler as needed when a new IFlurlClient instance is created. | ||
/// </summary> | ||
public virtual HttpMessageHandler CreateInnerHandler() { | ||
// 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(); | ||
|
||
if (handler.SupportsRedirectConfiguration) | ||
handler.AllowAutoRedirect = false; | ||
|
||
// #266 | ||
// deflate not working? see #474 | ||
if (handler.SupportsAutomaticDecompression) | ||
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; | ||
|
||
try { handler.UseCookies = false; } | ||
catch (PlatformNotSupportedException) { } // look out for WASM platforms (#543) | ||
#endif | ||
return handler; | ||
} | ||
} | ||
} |
Oops, something went wrong.