ConfigureClient, plus lots of comments and some more tests

This commit is contained in:
tmenier 2015-09-20 11:54:16 -05:00
parent 03445902c1
commit b2b3fa73d1
8 changed files with 122 additions and 62 deletions

View File

@ -4,6 +4,7 @@ using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Flurl.Http.Configuration;
using Flurl.Util;
namespace Flurl.Http
@ -41,6 +42,34 @@ namespace Flurl.Http
return client;
}
/// <summary>
/// Change FlurlHttpSettings for this client instance.
/// </summary>
/// <param name="action">Action defining the settings changes.</param>
/// <returns>The FlurlClient with the modified HttpClient</returns>
public static FlurlClient ConfigureClient(this FlurlClient client, Action<FlurlHttpSettings> action) {
action(client.Settings);
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and allows changing the FlurlHttpSettings associated with the instance.
/// </summary>
/// <param name="action">Action defining the settings changes.</param>
/// <returns>The FlurlClient with the modified HttpClient</returns>
public static FlurlClient ConfigureClient(this Url url, Action<FlurlHttpSettings> action) {
return new FlurlClient(url, true).ConfigureClient(action);
}
/// <summary>
/// Creates a FlurlClient from the URL and allows changing the FlurlHttpSettings associated with the instance.
/// </summary>
/// <param name="action">Action defining the settings changes.</param>
/// <returns>The FlurlClient with the modified HttpClient</returns>
public static FlurlClient ConfigureClient(this string url, Action<FlurlHttpSettings> action) {
return new FlurlClient(url, true).ConfigureClient(action);
}
/// <summary>
/// Provides access to modifying the underlying HttpClient.
/// </summary>

View File

@ -6,7 +6,11 @@ using Flurl.Util;
namespace Flurl.Http.Configuration
{
public class DefaultUrlEncodedSerializer : ISerializer
/// <summary>
/// ISerializer implementation that converts an object representing name/value pairs to a URL-encoded string.
/// Default serializer used in calls to PostUrlEncodedAsync, etc.
/// </summary>
public class DefaultUrlEncodedSerializer : ISerializer
{
public string Serialize(object obj) {
var sb = new StringBuilder();

View File

@ -5,10 +5,22 @@ using System.Text;
namespace Flurl.Http.Configuration
{
/// <summary>
/// Contract for serializing and deserializing objects.
/// </summary>
public interface ISerializer
{
/// <summary>
/// Serializes an object to a string representation.
/// </summary>
string Serialize(object obj);
/// <summary>
/// Deserializes an object from a string representation.
/// </summary>
T Deserialize<T>(string s);
/// <summary>
/// Deserializes an object from a stream representation.
/// </summary>
T Deserialize<T>(Stream stream);
}
}

View File

@ -7,7 +7,8 @@ using Newtonsoft.Json;
namespace Flurl.Http.Configuration
{
/// <summary>
/// ISerializer implementation that uses Newtonsoft Json.NET. Used as Flurl.Http's default JSON serializer.
/// ISerializer implementation that uses Newtonsoft Json.NET.
/// Default serializer used in calls to GetJsonAsync, PostJsonAsync, etc.
/// </summary>
public class NewtonsoftJsonSerializer : ISerializer
{

View File

@ -8,13 +8,18 @@ namespace Flurl.Http
{
public static class HttpRequestMessageExtensions
{
public static void SetFlurlSettings(this HttpRequestMessage request, FlurlHttpSettings settings) {
request.Properties["FlurlSettings"] = settings;
private const string SETTINGS_KEY = "FlurlSettings";
internal static void SetFlurlSettings(this HttpRequestMessage request, FlurlHttpSettings settings) {
request.Properties[SETTINGS_KEY] = settings;
}
/// <summary>
/// Gets the FlurlSettings object associated with this HttpRequestMessage.
/// </summary>
public static FlurlHttpSettings GetFlurlSettings(this HttpRequestMessage request) {
object settings;
return request.Properties.TryGetValue("FlurlSettings", out settings) ? (FlurlHttpSettings)settings : FlurlHttp.GlobalSettings;
return request.Properties.TryGetValue(SETTINGS_KEY, out settings) ? (FlurlHttpSettings)settings : FlurlHttp.GlobalSettings;
}
}
}

View File

@ -16,7 +16,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Http\RealHttpTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Http\FlurlClientTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Http\GetTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Http\SettingsTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Http\GlobalConfigTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Http\HeadTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Http\HttpTestFixtureBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Http\PostTests.cs" />

View File

@ -4,14 +4,19 @@ using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Flurl.Http;
using Flurl.Http.Configuration;
using Flurl.Http.Testing;
using NUnit.Framework;
namespace Flurl.Test.Http
{
[TestFixture]
public class ClientConfigTests
public class ClientConfigTestsBase : ConfigTestsBase
{
protected override FlurlHttpSettings GetSettings() {
return GetClient().Settings;
}
[Test]
public void can_set_timeout() {
var client = "http://www.api.com".WithTimeout(TimeSpan.FromSeconds(15));
@ -77,16 +82,25 @@ namespace Flurl.Test.Http
}
[Test]
public async Task can_allow_non_success_status() {
public async Task can_allow_specific_http_status() {
using (var test = new HttpTest()) {
test.RespondWith(404, "Nothing to see here");
// no exception = pass
await "http://www.api.com"
.AllowHttpStatus(HttpStatusCode.Conflict, HttpStatusCode.NotFound)
.DeleteAsync();
}
}
[Test, ExpectedException(typeof(FlurlHttpException))]
public async Task can_clear_non_success_status() {
using (var test = new HttpTest()) {
test.RespondWith(418, "I'm a teapot");
try {
var result = await "http://www.api.com".AllowHttpStatus("1xx,300-500").GetAsync();
Assert.IsFalse(result.IsSuccessStatusCode);
}
catch (Exception) {
Assert.Fail("Exception should not have been thrown.");
}
// allow 4xx
var client = "http://www.api.com".AllowHttpStatus("4xx");
// but then disallow it
client.Settings.AllowedHttpStatusRange = null;
await client.GetAsync();
}
}
@ -105,24 +119,11 @@ namespace Flurl.Test.Http
}
[Test, ExpectedException(typeof(FlurlHttpException))]
public async Task can_clear_non_success_status() {
public async Task can_override_settings_fluently() {
using (var test = new HttpTest()) {
test.RespondWith(418, "I'm a teapot");
// allow 4xx
var client = "http://www.api.com".AllowHttpStatus("4xx");
// but then disallow it
client.Settings.AllowedHttpStatusRange = null;
await client.GetAsync();
}
}
[Test]
public async Task can_allow_specific_http_status() {
using (var test = new HttpTest()) {
test.RespondWith(404, "Nothing to see here");
// allow 404
var client = "http://www.api.com".AllowHttpStatus(HttpStatusCode.Conflict, HttpStatusCode.NotFound);
await client.DeleteAsync();
FlurlHttp.GlobalSettings.AllowedHttpStatusRange = "*";
test.RespondWith(500, "epic fail");
await "http://www.api.com".ConfigureClient(c => c.AllowedHttpStatusRange = "2xx").GetAsync();
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Flurl.Http;
@ -8,30 +9,41 @@ using NUnit.Framework;
namespace Flurl.Test.Http
{
[TestFixture]
public class GlobalConfigTests
/// <summary>
/// All global settings can also be set at the client level, so this base class allows ClientConfigTests to
/// inherit all the same tests.
/// </summary>
public abstract class ConfigTestsBase
{
protected abstract FlurlHttpSettings GetSettings();
private FlurlClient _client;
protected FlurlClient GetClient() {
if (_client == null)
_client = new FlurlClient("http://www.api.com");
return _client;
}
[TearDown]
public void ResetDefaults() {
FlurlHttp.GlobalSettings.ResetDefaults();
GetSettings().ResetDefaults();
_client = null;
}
[Test]
public void can_provide_custom_httpclient_factory() {
FlurlHttp.GlobalSettings.HttpClientFactory = new SomeCustomHttpClientFactory();
var client = new FlurlClient("http://www.api.com");
Assert.IsInstanceOf<SomeCustomHttpClient>(client.HttpClient);
Assert.IsInstanceOf<SomeCustomMessageHandler>(client.HttpMessageHandler);
GetSettings().HttpClientFactory = new SomeCustomHttpClientFactory();
Assert.IsInstanceOf<SomeCustomHttpClient>(GetClient().HttpClient);
Assert.IsInstanceOf<SomeCustomMessageHandler>(GetClient().HttpMessageHandler);
}
[Test]
public async Task can_allow_non_success_status() {
using (var test = new HttpTest()) {
FlurlHttp.GlobalSettings.AllowedHttpStatusRange = "4xx";
GetSettings().AllowedHttpStatusRange = "4xx";
test.RespondWith(418, "I'm a teapot");
try {
var result = await "http://www.api.com".GetAsync();
var result = await GetClient().GetAsync();
Assert.IsFalse(result.IsSuccessStatusCode);
}
catch (Exception) {
@ -45,12 +57,12 @@ namespace Flurl.Test.Http
var callbackCalled = false;
using (var test = new HttpTest()) {
test.RespondWith("ok");
FlurlHttp.GlobalSettings.BeforeCall = req => {
GetSettings().BeforeCall = req => {
CollectionAssert.IsNotEmpty(test.ResponseQueue); // verifies that callback is running before HTTP call is made
callbackCalled = true;
};
Assert.IsFalse(callbackCalled);
await "http://www.api.com".GetAsync();
await GetClient().GetAsync();
Assert.IsTrue(callbackCalled);
}
}
@ -60,12 +72,12 @@ namespace Flurl.Test.Http
var callbackCalled = false;
using (var test = new HttpTest()) {
test.RespondWith("ok");
FlurlHttp.GlobalSettings.AfterCall = call => {
GetSettings().AfterCall = call => {
CollectionAssert.IsEmpty(test.ResponseQueue); // verifies that callback is running after HTTP call is made
callbackCalled = true;
};
Assert.IsFalse(callbackCalled);
await "http://www.api.com".GetAsync();
await GetClient().GetAsync();
Assert.IsTrue(callbackCalled);
}
}
@ -76,14 +88,14 @@ namespace Flurl.Test.Http
var callbackCalled = false;
using (var test = new HttpTest()) {
test.RespondWith(500, "server error");
FlurlHttp.GlobalSettings.OnError = call => {
GetSettings().OnError = call => {
CollectionAssert.IsEmpty(test.ResponseQueue); // verifies that callback is running after HTTP call is made
callbackCalled = true;
call.ExceptionHandled = markExceptionHandled;
};
Assert.IsFalse(callbackCalled);
try {
await "http://www.api.com".GetAsync();
await GetClient().GetAsync();
Assert.IsTrue(callbackCalled, "OnError was never called");
Assert.IsTrue(markExceptionHandled, "ExceptionHandled was marked false in callback, but exception was not propagated.");
}
@ -97,12 +109,12 @@ namespace Flurl.Test.Http
[Test]
public async Task can_disable_exception_behavior() {
using (var test = new HttpTest()) {
FlurlHttp.GlobalSettings.OnError = call => {
GetSettings().OnError = call => {
call.ExceptionHandled = true;
};
test.RespondWith(500, "server error");
try {
var result = await "http://www.api.com".GetAsync();
var result = await GetClient().GetAsync();
Assert.IsFalse(result.IsSuccessStatusCode);
}
catch (FlurlHttpException) {
@ -111,18 +123,6 @@ namespace Flurl.Test.Http
}
}
[Test]
public async Task client_can_override_global_settings() {
var overridden = false;
using (new HttpTest()) {
FlurlHttp.GlobalSettings.AfterCall = _ => overridden = false;
var fc = new FlurlClient("http://www.api.com");
fc.Settings.AfterCall = _ => overridden = true;
await fc.GetAsync();
Assert.True(overridden);
}
}
private class SomeCustomHttpClientFactory : IHttpClientFactory
{
public HttpClient CreateClient(Url url, HttpMessageHandler handler) {
@ -137,4 +137,12 @@ namespace Flurl.Test.Http
private class SomeCustomHttpClient : HttpClient { }
private class SomeCustomMessageHandler : HttpClientHandler { }
}
[TestFixture]
public class GlobalConfigTestsBase : ConfigTestsBase
{
protected override FlurlHttpSettings GetSettings() {
return FlurlHttp.GlobalSettings;
}
}
}