re-introduce HttpClientFactory, keep FlurlClientFactory.Get separate. (the former makes sense at FlurlClient settings level; latter does not)

This commit is contained in:
Todd Menier 2017-08-13 15:54:22 -05:00
parent a2656d4056
commit ad8c051543
11 changed files with 115 additions and 74 deletions

View File

@ -23,7 +23,7 @@ namespace Flurl.Test.Http
[Test]
public void can_provide_custom_client_factory() {
GetSettings().FlurlClientFactory = new SomeCustomFlurlClientFactory();
GetSettings().HttpClientFactory = new SomeCustomHttpClientFactory();
var client = new FlurlClient();
Assert.IsInstanceOf<SomeCustomHttpClient>(client.HttpClient);
Assert.IsInstanceOf<SomeCustomMessageHandler>(client.HttpMessageHandler);
@ -115,7 +115,7 @@ namespace Flurl.Test.Http
}
}
private class SomeCustomFlurlClientFactory : IFlurlClientFactory
private class SomeCustomHttpClientFactory : IHttpClientFactory
{
public HttpClient CreateHttpClient(HttpMessageHandler handler) {
return new SomeCustomHttpClient();
@ -124,10 +124,6 @@ namespace Flurl.Test.Http
public HttpMessageHandler CreateMessageHandler() {
return new SomeCustomMessageHandler();
}
public IFlurlClient GetClient(Url url) {
return new FlurlClient();
}
}
private class SomeCustomHttpClient : HttpClient { }

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Net.Http;
namespace Flurl.Http.Configuration
{
@ -12,31 +11,13 @@ namespace Flurl.Http.Configuration
{
private static readonly ConcurrentDictionary<string, IFlurlClient> _clients = new ConcurrentDictionary<string, IFlurlClient>();
/// <summary>
/// Override in custom factory to customize the creation of HttpClient used in all Flurl HTTP calls.
/// In order not to lose Flurl.Http functionality, it is recommended to call base.CreateClient and
/// customize the result.
/// </summary>
public virtual HttpClient CreateHttpClient(HttpMessageHandler handler) {
return new HttpClient(new FlurlMessageHandler(handler));
}
/// <summary>
/// Override in custom factory to customize the creation of HttpClientHandler used in all Flurl HTTP calls.
/// In order not to lose Flurl.Http functionality, it is recommended to call base.CreateMessageHandler and
/// customize the result.
/// </summary>
public virtual HttpMessageHandler CreateMessageHandler() {
return new HttpClientHandler();
}
/// <summary>
/// Uses a caching strategy of one FlurlClient per host. This maximizes reuse of underlying
/// HttpClient/Handler while allowing things like cookies to be host-specific.
/// </summary>
/// <param name="url">The URL.</param>
/// <returns>The FlurlClient instance.</returns>
public virtual IFlurlClient GetClient(Url url) {
public virtual IFlurlClient Get(Url url) {
var key = new Uri(url).Host;
return _clients.GetOrAdd(key, _ => new FlurlClient());
}

View File

@ -0,0 +1,30 @@
using System.Net.Http;
namespace Flurl.Http.Configuration
{
/// <summary>
/// Default implementation of IHttpClientFactory used by FlurlHttp. The created HttpClient includes hooks
/// that enable FlurlHttp's testing features and respect its configuration settings. Therefore, custom factories
/// should inherit from this class, rather than implementing IHttpClientFactory directly.
/// </summary>
public class DefaultHttpClientFactory : IHttpClientFactory
{
/// <summary>
/// Override in custom factory to customize the creation of HttpClient used in all Flurl HTTP calls.
/// In order not to lose Flurl.Http functionality, it is recommended to call base.CreateClient and
/// customize the result.
/// </summary>
public virtual HttpClient CreateHttpClient(HttpMessageHandler handler) {
return new HttpClient(new FlurlMessageHandler(handler));
}
/// <summary>
/// Override in custom factory to customize the creation of HttpClientHandler used in all Flurl HTTP calls.
/// In order not to lose Flurl.Http functionality, it is recommended to call base.CreateMessageHandler and
/// customize the result.
/// </summary>
public virtual HttpMessageHandler CreateMessageHandler() {
return new HttpClientHandler();
}
}
}

View File

@ -21,7 +21,7 @@ namespace Flurl.Http.Configuration
private IDictionary<string, object> _vals = new Dictionary<string, object>();
private static FlurlHttpSettings _baseDefaults = new FlurlHttpSettings(null) {
FlurlClientFactory = new DefaultFlurlClientFactory(),
HttpClientFactory = new DefaultHttpClientFactory(),
CookiesEnabled = false,
JsonSerializer = new NewtonsoftJsonSerializer(null),
UrlEncodedSerializer = new DefaultUrlEncodedSerializer()
@ -43,13 +43,12 @@ namespace Flurl.Http.Configuration
/// <summary>
/// Gets or sets a factory used to create HttpClient object used in Flurl HTTP calls. Default value
/// is an instance of DefaultFlurlClientFactory. Custom factory implementations should generally
/// inherit from DefaultFlurlClientFactory, call base.CreateClient, and manipulate the returned HttpClient,
/// otherwise functionality such as callbacks and most testing features will be lost.
/// is an instance of DefaultHttpClientFactory. Custom factory implementations should generally
/// inherit from DefaultHttpClientFactory, the base implementations, and only customize as needed.
/// </summary>
public IFlurlClientFactory FlurlClientFactory {
get => Get(() => FlurlClientFactory);
set => Set(() => FlurlClientFactory, value);
public IHttpClientFactory HttpClientFactory {
get => Get(() => HttpClientFactory);
set => Set(() => HttpClientFactory, value);
}
/// <summary>
@ -174,4 +173,16 @@ namespace Flurl.Http.Configuration
return this;
}
}
/// <summary>
/// Global default settings for Flurl.Http
/// </summary>
public class GlobalFlurlHttpSettings : FlurlHttpSettings
{
/// <summary>
/// Gets or sets the factory that defines creating, caching, and reusing FlurlClient instances and,
/// by proxy, HttpClient instances.
/// </summary>
public IFlurlClientFactory FlurlClientFactory { get; set; } = new DefaultFlurlClientFactory();
}
}

View File

@ -1,33 +1,16 @@
using System.Net.Http;
namespace Flurl.Http.Configuration
namespace Flurl.Http.Configuration
{
/// <summary>
/// Interface defining creation of HttpClient and HttpMessageHandler used in all Flurl HTTP calls.
/// Implementation can be added via FlurlHttp.Configure. However, in order not to lose much of
/// Flurl.Http's functionality, it's almost always best to inherit DefaultFlurlClientFactory and
/// extend the base implementations, rather than implementing this interface directly.
/// Interface for defining a strategy for creating, caching, and reusing IFlurlClient instances and,
/// by proxy, their underlying HttpClient instances.
/// </summary>
public interface IFlurlClientFactory
{
/// <summary>
/// Creates the client.
/// </summary>
/// <param name="handler">The message handler being used in this call</param>
/// <returns></returns>
HttpClient CreateHttpClient(HttpMessageHandler handler);
/// <summary>
/// Creates the message handler.
/// </summary>
/// <returns></returns>
HttpMessageHandler CreateMessageHandler();
/// <summary>
/// Strategy to create an HttpClient or reuse an exisitng one, based on URL being called.
/// Strategy to create a FlurlClient or reuse an exisitng one, based on URL being called.
/// </summary>
/// <param name="url">The URL being called.</param>
/// <returns></returns>
IFlurlClient GetClient(Url url);
IFlurlClient Get(Url url);
}
}

View File

@ -0,0 +1,28 @@
using System.Net.Http;
namespace Flurl.Http.Configuration
{
/// <summary>
/// Interface defining creation of HttpClient and HttpMessageHandler used in all Flurl HTTP calls.
/// Implementation can be added via FlurlHttp.Configure. However, in order not to lose much of
/// Flurl.Http's functionality, it's almost always best to inherit DefaultHttpClientFactory and
/// extend the base implementations, rather than implementing this interface directly.
/// </summary>
public interface IHttpClientFactory
{
/// <summary>
/// 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.
/// </summary>
/// <param name="handler">The HttpMessageHandler used to construct the HttpClient.</param>
/// <returns></returns>
HttpClient CreateHttpClient(HttpMessageHandler handler);
/// <summary>
/// Defines how the
/// </summary>
/// <returns></returns>
HttpMessageHandler CreateMessageHandler();
}
}

View File

@ -40,9 +40,9 @@ namespace Flurl.Http
/// </summary>
/// <param name="settings">The FlurlHttpSettings associated with this instance.</param>
public FlurlClient(FlurlHttpSettings settings = null) {
Settings = settings ?? new FlurlHttpSettings().Merge(HttpTest.Current?.Settings ?? FlurlHttp.GlobalSettings);
HttpMessageHandler = Settings.FlurlClientFactory.CreateMessageHandler();
HttpClient = Settings.FlurlClientFactory.CreateHttpClient(HttpMessageHandler);
Settings = settings ?? new FlurlHttpSettings(FlurlHttp.GlobalSettings);
HttpMessageHandler = Settings.HttpClientFactory.CreateMessageHandler();
HttpClient = Settings.HttpClientFactory.CreateHttpClient(HttpMessageHandler);
}
/// <summary>

View File

@ -2,6 +2,7 @@
using System.Net.Http;
using System.Threading.Tasks;
using Flurl.Http.Configuration;
using Flurl.Http.Testing;
namespace Flurl.Http
{
@ -13,13 +14,13 @@ namespace Flurl.Http
private static readonly object _configLock = new object();
private static readonly Task _completedTask = Task.FromResult(0);
private static Lazy<FlurlHttpSettings> _settings =
new Lazy<FlurlHttpSettings>(() => new FlurlHttpSettings());
private static Lazy<GlobalFlurlHttpSettings> _settings =
new Lazy<GlobalFlurlHttpSettings>(() => new GlobalFlurlHttpSettings());
/// <summary>
/// Globally configured Flurl.Http settings. Should normally be written to by calling FlurlHttp.Configure once application at startup.
/// </summary>
public static FlurlHttpSettings GlobalSettings => _settings.Value;
public static GlobalFlurlHttpSettings GlobalSettings => HttpTest.Current?.Settings ?? _settings.Value;
/// <summary>
/// Provides thread-safe access to Flurl.Http's global configuration settings. Should only be called once at application startup.

View File

@ -51,7 +51,7 @@ namespace Flurl.Http
/// <param name="url">The URL to call with this FlurlRequest instance.</param>
/// <param name="settings">The FlurlHttpSettings object used by this request.</param>
public FlurlRequest(Url url, FlurlHttpSettings settings = null) {
Settings = settings ?? new FlurlHttpSettings().Merge(HttpTest.Current?.Settings ?? FlurlHttp.GlobalSettings);
Settings = settings ?? new FlurlHttpSettings().Merge(FlurlHttp.GlobalSettings);
Url = url;
}
@ -71,7 +71,13 @@ namespace Flurl.Http
/// Gets or sets the IFlurlClient to use when sending the request.
/// </summary>
public IFlurlClient Client {
get => _client = _client ?? Settings.FlurlClientFactory.GetClient(Url);
get {
if (_client == null) {
_client = FlurlHttp.GlobalSettings.FlurlClientFactory.Get(Url);
Settings.Merge(_client.Settings);
}
return _client;
}
set {
_client = value;
Settings.Merge(_client.Settings);
@ -103,7 +109,6 @@ namespace Flurl.Http
/// <param name="completionOption">The HttpCompletionOption used in the request. Optional.</param>
/// <returns>A Task whose result is the received HttpResponseMessage.</returns>
public async Task<HttpResponseMessage> SendAsync(HttpMethod verb, HttpContent content = null, CancellationToken? cancellationToken = null, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
Settings.Merge(Client.Settings);
if (Settings.Timeout.HasValue)
Client.HttpClient.Timeout = Settings.Timeout.Value;
var request = new HttpRequestMessage(verb, Url) { Content = content };

View File

@ -20,9 +20,10 @@ namespace Flurl.Http.Testing
/// </summary>
/// <exception cref="Exception">A delegate callback throws an exception.</exception>
public HttpTest() {
Settings = new FlurlHttpSettings {
FlurlClientFactory = new TestFlurlClientFactory()
}.Merge(FlurlHttp.GlobalSettings);
Settings = new GlobalFlurlHttpSettings {
HttpClientFactory = new TestHttpClientFactory(),
FlurlClientFactory = new TestFlurlClientFactory()
};
ResponseQueue = new Queue<HttpResponseMessage>();
CallLog = new List<HttpCall>();
SetCurrentTest(this);
@ -31,7 +32,7 @@ namespace Flurl.Http.Testing
/// <summary>
/// Gets or sets the FlurlHttpSettings object used by this test.
/// </summary>
public FlurlHttpSettings Settings { get; set; }
public GlobalFlurlHttpSettings Settings { get; set; }
/// <summary>
/// Gets the current HttpTest from the logical (async) call context

View File

@ -1,17 +1,14 @@
using System;
using System.Diagnostics;
using System.Net.Http;
using Flurl.Http.Configuration;
namespace Flurl.Http.Testing
{
/// <summary>
/// Fake http client factory.
/// IHttpClientFactory implementation used to fake and record calls in tests.
/// </summary>
public class TestFlurlClientFactory : DefaultFlurlClientFactory
public class TestHttpClientFactory : DefaultHttpClientFactory
{
private readonly Lazy<FlurlClient> _client = new Lazy<FlurlClient>(() => new FlurlClient());
/// <summary>
/// Creates an instance of FakeHttpMessageHander, which prevents actual HTTP calls from being made.
/// </summary>
@ -19,13 +16,21 @@ namespace Flurl.Http.Testing
public override HttpMessageHandler CreateMessageHandler() {
return new FakeHttpMessageHandler();
}
}
/// <summary>
/// IFlurlClientFactory implementation used to fake and record calls in tests.
/// </summary>
public class TestFlurlClientFactory : DefaultFlurlClientFactory
{
private readonly Lazy<FlurlClient> _client = new Lazy<FlurlClient>(() => new FlurlClient());
/// <summary>
/// Returns the FlurlClient sigleton used for testing
/// </summary>
/// <param name="url">The URL.</param>
/// <returns>The FlurlClient instance.</returns>
public override IFlurlClient GetClient(Url url) {
public override IFlurlClient Get(Url url) {
return _client.Value;
}
}