Issue #65, solved by creating a copy of FlurlClient from WithUrl to get thread safety, rather than making Dispose virtual

This commit is contained in:
tmenier 2016-04-17 15:08:17 -05:00
parent fc231312e0
commit ea9c5ddf8d
3 changed files with 555 additions and 509 deletions

View File

@ -1,373 +1,378 @@
using System;
using System.Linq;
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
{
public static class ClientConfigExtensions
{
/// <summary>
/// Fluently specify that an existing FlurlClient should be used to call the Url, rather than creating a new one.
/// Enables re-using the underlying HttpClient.
/// </summary>
/// <param name="fc">The FlurlClient to use in calling the Url</param>
/// <returns></returns>
public static FlurlClient WithClient(this Url url, FlurlClient fc) {
fc.Url = url;
return fc;
}
/// <summary>
/// Fluently specify that an existing FlurlClient should be used to call the Url, rather than creating a new one.
/// Enables re-using the underlying HttpClient.
/// </summary>
/// <param name="fc">The FlurlClient to use in calling the Url</param>
/// <returns></returns>
public static FlurlClient WithClient(this string url, FlurlClient fc) {
return new Url(url).WithClient(fc);
}
/// <summary>
/// Fluently specify the URL to be called with the current FlurlClient instance.
/// </summary>
/// <param name="url">The Url to call.</param>
/// <returns></returns>
public static FlurlClient WithUrl(this FlurlClient client, Url url) {
client.Url = url;
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>
/// <param name="action">Action to perform on the HttpClient.</param>
/// <returns>The FlurlClient with the modified HttpClient</returns>
public static FlurlClient ConfigureHttpClient(this FlurlClient client, Action<HttpClient> action) {
action(client.HttpClient);
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and provides access to modifying the underlying HttpClient.
/// </summary>
/// <param name="action">Action to perform on the HttpClient.</param>
/// <returns>The FlurlClient with the modified HttpClient</returns>
public static FlurlClient ConfigureHttpClient(this Url url, Action<HttpClient> action) {
return new FlurlClient(url, true).ConfigureHttpClient(action);
}
/// <summary>
/// Creates a FlurlClient from the URL and provides access to modifying the underlying HttpClient.
/// </summary>
/// <param name="action">Action to perform on the HttpClient.</param>
/// <returns>The FlurlClient with the modified HttpClient</returns>
public static FlurlClient ConfigureHttpClient(this string url, Action<HttpClient> action) {
return new FlurlClient(url, true).ConfigureHttpClient(action);
}
/// <summary>
/// Sets the client timout to the specified timespan.
/// </summary>
/// <param name="timespan">Time to wait before the request times out.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithTimeout(this FlurlClient client, TimeSpan timespan) {
client.HttpClient.Timeout = timespan;
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and sets the client timout to the specified timespan.
/// </summary>
/// <param name="timespan">Time to wait before the request times out.</param>
/// <returns>The created FlurlClient.</returns>
public static FlurlClient WithTimeout(this Url url, TimeSpan timespan) {
return new FlurlClient(url, true).WithTimeout(timespan);
}
/// <summary>
/// Creates a FlurlClient from the URL and sets the client timout to the specified timespan.
/// </summary>
/// <param name="timespan">Time to wait before the request times out.</param>
/// <returns>The created FlurlClient.</returns>
public static FlurlClient WithTimeout(this string url, TimeSpan timespan) {
return new FlurlClient(url, true).WithTimeout(timespan);
}
/// <summary>
/// Sets the client timout to the specified number of seconds.
/// </summary>
/// <param name="seconds">Number of seconds to wait before the request times out.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithTimeout(this FlurlClient client, int seconds) {
return client.WithTimeout(TimeSpan.FromSeconds(seconds));
}
/// <summary>
/// Creates a FlurlClient from the URL and sets the client timout to the specified number of seconds.
/// </summary>
/// <param name="seconds">Number of seconds to wait before the request times out.</param>
/// <returns>The created FlurlClient.</returns>
public static FlurlClient WithTimeout(this Url url, int seconds) {
return new FlurlClient(url, true).WithTimeout(seconds);
}
/// <summary>
/// Creates a FlurlClient from the URL and sets the client timout to the specified number of seconds.
/// </summary>
/// <param name="seconds">Number of seconds to wait before the request times out.</param>
/// <returns>The created FlurlClient.</returns>
public static FlurlClient WithTimeout(this string url, int seconds) {
return new FlurlClient(url, true).WithTimeout(seconds);
}
/// <summary>
/// Sets an HTTP header to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="name">HTTP header name.</param>
/// <param name="value">HTTP header value.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithHeader(this FlurlClient client, string name, object value) {
var values = new[] { (value == null) ? null : value.ToString() };
client.HttpClient.DefaultRequestHeaders.Add(name, values);
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and sets an HTTP header to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="name">HTTP header name.</param>
/// <param name="value">HTTP header value.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithHeader(this Url url, string name, object value) {
return new FlurlClient(url, true).WithHeader(name, value);
}
/// <summary>
/// Creates a FlurlClient from the URL and sets an HTTP header to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="name">HTTP header name.</param>
/// <param name="value">HTTP header value.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithHeader(this string url, string name, object value) {
return new FlurlClient(url, true).WithHeader(name, value);
}
/// <summary>
/// Sets HTTP headers based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="headers">Names/values of HTTP headers to set. Typically an anonymous object or IDictionary.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithHeaders(this FlurlClient client, object headers) {
if (headers == null)
return client;
foreach (var kv in headers.ToKeyValuePairs()) {
if (kv.Value == null)
continue;
client.HttpClient.DefaultRequestHeaders.Add(kv.Key, new[] { kv.Value.ToString() });
}
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and sets HTTP headers based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="headers">Names/values of HTTP headers to set. Typically an anonymous object or IDictionary.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithHeaders(this Url url, object headers) {
return new FlurlClient(url, true).WithHeaders(headers);
}
/// <summary>
/// Creates a FlurlClient from the URL and sets HTTP headers based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="headers">Names/values of HTTP headers to set. Typically an anonymous object or IDictionary.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithHeaders(this string url, object headers) {
return new FlurlClient(url, true).WithHeaders(headers);
}
/// <summary>
/// Sets HTTP authorization header according to Basic Authentication protocol to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="username">Username of authenticating user.</param>
/// <param name="password">Password of authenticating user.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithBasicAuth(this FlurlClient client, string username, string password) {
// http://stackoverflow.com/questions/14627399/setting-authorization-header-of-httpclient
var value = Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("{0}:{1}", username, password)));
client.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", value);
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and sets HTTP authorization header according to Basic Authentication protocol to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="username">Username of authenticating user.</param>
/// <param name="password">Password of authenticating user.</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient WithBasicAuth(this Url url, string username, string password) {
return new FlurlClient(url, true).WithBasicAuth(username, password);
}
/// <summary>
/// Creates a FlurlClient from the URL and sets HTTP authorization header according to Basic Authentication protocol to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="username">Username of authenticating user.</param>
/// <param name="password">Password of authenticating user.</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient WithBasicAuth(this string url, string username, string password) {
return new FlurlClient(url, true).WithBasicAuth(username, password);
}
/// <summary>
/// Sets HTTP authorization header with acquired bearer token according to OAuth 2.0 specification to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="token">The acquired bearer token to pass.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithOAuthBearerToken(this FlurlClient client, string token) {
client.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and sets HTTP authorization header with acquired bearer token according to OAuth 2.0 specification to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="token">The acquired bearer token to pass.</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient WithOAuthBearerToken(this Url url, string token) {
return new FlurlClient(url, true).WithOAuthBearerToken(token);
}
/// <summary>
/// Creates a FlurlClient from the URL and sets HTTP authorization header with acquired bearer token according to OAuth 2.0 specification to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="token">The acquired bearer token to pass.</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient WithOAuthBearerToken(this string url, string token) {
return new FlurlClient(url, true).WithOAuthBearerToken(token);
}
/// <summary>
/// Adds a pattern representing an HTTP status code or range of codes which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.
/// </summary>
/// <param name="pattern">Examples: "3xx", "100,300,600", "100-299,6xx"</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient AllowHttpStatus(this FlurlClient client, string pattern) {
if (!string.IsNullOrWhiteSpace(pattern)) {
var current = client.Settings.AllowedHttpStatusRange;
if (string.IsNullOrWhiteSpace(current))
client.Settings.AllowedHttpStatusRange = pattern;
else
client.Settings.AllowedHttpStatusRange += "," + pattern;
}
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and adds a pattern representing an HTTP status code or range of codes which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.
/// </summary>
/// <param name="pattern">Examples: "3xx", "100,300,600", "100-299,6xx"</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient AllowHttpStatus(this Url url, string pattern) {
return new FlurlClient(url, true).AllowHttpStatus(pattern);
}
/// <summary>
/// Creates a FlurlClient from the URL and adds a pattern representing an HTTP status code or range of codes which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.
/// </summary>
/// <param name="pattern">Examples: "3xx", "100,300,600", "100-299,6xx"</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient AllowHttpStatus(this string url, string pattern) {
return new FlurlClient(url, true).AllowHttpStatus(pattern);
}
/// <summary>
/// Adds an <see cref="HttpStatusCode"/> which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.
/// </summary>
/// <param name="statusCodes">Examples: HttpStatusCode.NotFound</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient AllowHttpStatus(this FlurlClient client, params HttpStatusCode[] statusCodes) {
var pattern = string.Join(",", statusCodes.Select(c => (int)c));
return AllowHttpStatus(client, pattern);
}
/// <summary>
/// Adds an <see cref="HttpStatusCode"/> which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.
/// </summary>
/// <param name="statusCodes">Examples: HttpStatusCode.NotFound</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient AllowHttpStatus(this Url url, params HttpStatusCode[] statusCodes) {
return new FlurlClient(url, true).AllowHttpStatus(statusCodes);
}
/// <summary>
/// Adds an <see cref="HttpStatusCode"/> which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.
/// </summary>
/// <param name="statusCodes">Examples: HttpStatusCode.NotFound</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient AllowHttpStatus(this string url, params HttpStatusCode[] statusCodes) {
return new FlurlClient(url, true).AllowHttpStatus(statusCodes);
}
/// <summary>
/// Prevents a FlurlHttpException from being thrown on any completed response, regardless of the HTTP status code.
/// </summary>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient AllowAnyHttpStatus(this FlurlClient client) {
client.Settings.AllowedHttpStatusRange = "*";
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and prevents a FlurlHttpException from being thrown on any completed response, regardless of the HTTP status code.
/// </summary>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient AllowAnyHttpStatus(this Url url) {
return new FlurlClient(url, true).AllowAnyHttpStatus();
}
/// <summary>
/// Creates a FlurlClient from the URL and prevents a FlurlHttpException from being thrown on any completed response, regardless of the HTTP status code.
/// </summary>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient AllowAnyHttpStatus(this string url) {
return new FlurlClient(url, true).AllowAnyHttpStatus();
}
}
}
using System;
using System.Linq;
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
{
public static class ClientConfigExtensions
{
/// <summary>
/// Fluently specify that an existing FlurlClient should be used to call the Url, rather than creating a new one.
/// Enables re-using the underlying HttpClient.
/// </summary>
/// <param name="fc">The FlurlClient to use in calling the Url</param>
/// <returns></returns>
public static FlurlClient WithClient(this Url url, FlurlClient fc) {
fc.Url = url;
return fc;
}
/// <summary>
/// Fluently specify that an existing FlurlClient should be used to call the Url, rather than creating a new one.
/// Enables re-using the underlying HttpClient.
/// </summary>
/// <param name="fc">The FlurlClient to use in calling the Url</param>
/// <returns></returns>
public static FlurlClient WithClient(this string url, FlurlClient fc) {
return new Url(url).WithClient(fc);
}
/// <summary>
/// Returns a new FlurlClient where all state (HttpClient, etc) is shared but with a different URL.
/// Allows you to re-use the underlying HttpClient instance (such as to share cookies, etc) with
/// different URLs in a thread-safe way.
/// </summary>
/// <param name="url">The Url to call.</param>
/// <returns></returns>
public static FlurlClient WithUrl(this FlurlClient client, Url url) {
var fc = client.Clone();
fc.Url = url;
// prevent the new client from automatically disposing the parent's HttpClient
fc.AutoDispose = false;
return fc;
}
/// <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>
/// <param name="action">Action to perform on the HttpClient.</param>
/// <returns>The FlurlClient with the modified HttpClient</returns>
public static FlurlClient ConfigureHttpClient(this FlurlClient client, Action<HttpClient> action) {
action(client.HttpClient);
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and provides access to modifying the underlying HttpClient.
/// </summary>
/// <param name="action">Action to perform on the HttpClient.</param>
/// <returns>The FlurlClient with the modified HttpClient</returns>
public static FlurlClient ConfigureHttpClient(this Url url, Action<HttpClient> action) {
return new FlurlClient(url, true).ConfigureHttpClient(action);
}
/// <summary>
/// Creates a FlurlClient from the URL and provides access to modifying the underlying HttpClient.
/// </summary>
/// <param name="action">Action to perform on the HttpClient.</param>
/// <returns>The FlurlClient with the modified HttpClient</returns>
public static FlurlClient ConfigureHttpClient(this string url, Action<HttpClient> action) {
return new FlurlClient(url, true).ConfigureHttpClient(action);
}
/// <summary>
/// Sets the client timout to the specified timespan.
/// </summary>
/// <param name="timespan">Time to wait before the request times out.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithTimeout(this FlurlClient client, TimeSpan timespan) {
client.HttpClient.Timeout = timespan;
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and sets the client timout to the specified timespan.
/// </summary>
/// <param name="timespan">Time to wait before the request times out.</param>
/// <returns>The created FlurlClient.</returns>
public static FlurlClient WithTimeout(this Url url, TimeSpan timespan) {
return new FlurlClient(url, true).WithTimeout(timespan);
}
/// <summary>
/// Creates a FlurlClient from the URL and sets the client timout to the specified timespan.
/// </summary>
/// <param name="timespan">Time to wait before the request times out.</param>
/// <returns>The created FlurlClient.</returns>
public static FlurlClient WithTimeout(this string url, TimeSpan timespan) {
return new FlurlClient(url, true).WithTimeout(timespan);
}
/// <summary>
/// Sets the client timout to the specified number of seconds.
/// </summary>
/// <param name="seconds">Number of seconds to wait before the request times out.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithTimeout(this FlurlClient client, int seconds) {
return client.WithTimeout(TimeSpan.FromSeconds(seconds));
}
/// <summary>
/// Creates a FlurlClient from the URL and sets the client timout to the specified number of seconds.
/// </summary>
/// <param name="seconds">Number of seconds to wait before the request times out.</param>
/// <returns>The created FlurlClient.</returns>
public static FlurlClient WithTimeout(this Url url, int seconds) {
return new FlurlClient(url, true).WithTimeout(seconds);
}
/// <summary>
/// Creates a FlurlClient from the URL and sets the client timout to the specified number of seconds.
/// </summary>
/// <param name="seconds">Number of seconds to wait before the request times out.</param>
/// <returns>The created FlurlClient.</returns>
public static FlurlClient WithTimeout(this string url, int seconds) {
return new FlurlClient(url, true).WithTimeout(seconds);
}
/// <summary>
/// Sets an HTTP header to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="name">HTTP header name.</param>
/// <param name="value">HTTP header value.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithHeader(this FlurlClient client, string name, object value) {
var values = new[] { (value == null) ? null : value.ToString() };
client.HttpClient.DefaultRequestHeaders.Add(name, values);
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and sets an HTTP header to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="name">HTTP header name.</param>
/// <param name="value">HTTP header value.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithHeader(this Url url, string name, object value) {
return new FlurlClient(url, true).WithHeader(name, value);
}
/// <summary>
/// Creates a FlurlClient from the URL and sets an HTTP header to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="name">HTTP header name.</param>
/// <param name="value">HTTP header value.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithHeader(this string url, string name, object value) {
return new FlurlClient(url, true).WithHeader(name, value);
}
/// <summary>
/// Sets HTTP headers based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="headers">Names/values of HTTP headers to set. Typically an anonymous object or IDictionary.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithHeaders(this FlurlClient client, object headers) {
if (headers == null)
return client;
foreach (var kv in headers.ToKeyValuePairs()) {
if (kv.Value == null)
continue;
client.HttpClient.DefaultRequestHeaders.Add(kv.Key, new[] { kv.Value.ToString() });
}
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and sets HTTP headers based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="headers">Names/values of HTTP headers to set. Typically an anonymous object or IDictionary.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithHeaders(this Url url, object headers) {
return new FlurlClient(url, true).WithHeaders(headers);
}
/// <summary>
/// Creates a FlurlClient from the URL and sets HTTP headers based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="headers">Names/values of HTTP headers to set. Typically an anonymous object or IDictionary.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithHeaders(this string url, object headers) {
return new FlurlClient(url, true).WithHeaders(headers);
}
/// <summary>
/// Sets HTTP authorization header according to Basic Authentication protocol to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="username">Username of authenticating user.</param>
/// <param name="password">Password of authenticating user.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithBasicAuth(this FlurlClient client, string username, string password) {
// http://stackoverflow.com/questions/14627399/setting-authorization-header-of-httpclient
var value = Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("{0}:{1}", username, password)));
client.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", value);
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and sets HTTP authorization header according to Basic Authentication protocol to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="username">Username of authenticating user.</param>
/// <param name="password">Password of authenticating user.</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient WithBasicAuth(this Url url, string username, string password) {
return new FlurlClient(url, true).WithBasicAuth(username, password);
}
/// <summary>
/// Creates a FlurlClient from the URL and sets HTTP authorization header according to Basic Authentication protocol to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="username">Username of authenticating user.</param>
/// <param name="password">Password of authenticating user.</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient WithBasicAuth(this string url, string username, string password) {
return new FlurlClient(url, true).WithBasicAuth(username, password);
}
/// <summary>
/// Sets HTTP authorization header with acquired bearer token according to OAuth 2.0 specification to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="token">The acquired bearer token to pass.</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient WithOAuthBearerToken(this FlurlClient client, string token) {
client.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and sets HTTP authorization header with acquired bearer token according to OAuth 2.0 specification to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="token">The acquired bearer token to pass.</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient WithOAuthBearerToken(this Url url, string token) {
return new FlurlClient(url, true).WithOAuthBearerToken(token);
}
/// <summary>
/// Creates a FlurlClient from the URL and sets HTTP authorization header with acquired bearer token according to OAuth 2.0 specification to be sent with all requests made with this FlurlClient.
/// </summary>
/// <param name="token">The acquired bearer token to pass.</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient WithOAuthBearerToken(this string url, string token) {
return new FlurlClient(url, true).WithOAuthBearerToken(token);
}
/// <summary>
/// Adds a pattern representing an HTTP status code or range of codes which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.
/// </summary>
/// <param name="pattern">Examples: "3xx", "100,300,600", "100-299,6xx"</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient AllowHttpStatus(this FlurlClient client, string pattern) {
if (!string.IsNullOrWhiteSpace(pattern)) {
var current = client.Settings.AllowedHttpStatusRange;
if (string.IsNullOrWhiteSpace(current))
client.Settings.AllowedHttpStatusRange = pattern;
else
client.Settings.AllowedHttpStatusRange += "," + pattern;
}
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and adds a pattern representing an HTTP status code or range of codes which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.
/// </summary>
/// <param name="pattern">Examples: "3xx", "100,300,600", "100-299,6xx"</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient AllowHttpStatus(this Url url, string pattern) {
return new FlurlClient(url, true).AllowHttpStatus(pattern);
}
/// <summary>
/// Creates a FlurlClient from the URL and adds a pattern representing an HTTP status code or range of codes which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.
/// </summary>
/// <param name="pattern">Examples: "3xx", "100,300,600", "100-299,6xx"</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient AllowHttpStatus(this string url, string pattern) {
return new FlurlClient(url, true).AllowHttpStatus(pattern);
}
/// <summary>
/// Adds an <see cref="HttpStatusCode"/> which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.
/// </summary>
/// <param name="statusCodes">Examples: HttpStatusCode.NotFound</param>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient AllowHttpStatus(this FlurlClient client, params HttpStatusCode[] statusCodes) {
var pattern = string.Join(",", statusCodes.Select(c => (int)c));
return AllowHttpStatus(client, pattern);
}
/// <summary>
/// Adds an <see cref="HttpStatusCode"/> which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.
/// </summary>
/// <param name="statusCodes">Examples: HttpStatusCode.NotFound</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient AllowHttpStatus(this Url url, params HttpStatusCode[] statusCodes) {
return new FlurlClient(url, true).AllowHttpStatus(statusCodes);
}
/// <summary>
/// Adds an <see cref="HttpStatusCode"/> which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.
/// </summary>
/// <param name="statusCodes">Examples: HttpStatusCode.NotFound</param>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient AllowHttpStatus(this string url, params HttpStatusCode[] statusCodes) {
return new FlurlClient(url, true).AllowHttpStatus(statusCodes);
}
/// <summary>
/// Prevents a FlurlHttpException from being thrown on any completed response, regardless of the HTTP status code.
/// </summary>
/// <returns>The modified FlurlClient.</returns>
public static FlurlClient AllowAnyHttpStatus(this FlurlClient client) {
client.Settings.AllowedHttpStatusRange = "*";
return client;
}
/// <summary>
/// Creates a FlurlClient from the URL and prevents a FlurlHttpException from being thrown on any completed response, regardless of the HTTP status code.
/// </summary>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient AllowAnyHttpStatus(this Url url) {
return new FlurlClient(url, true).AllowAnyHttpStatus();
}
/// <summary>
/// Creates a FlurlClient from the URL and prevents a FlurlHttpException from being thrown on any completed response, regardless of the HTTP status code.
/// </summary>
/// <returns>The new FlurlClient.</returns>
public static FlurlClient AllowAnyHttpStatus(this string url) {
return new FlurlClient(url, true).AllowAnyHttpStatus();
}
}
}

View File

@ -25,6 +25,20 @@ namespace Flurl.Http
public FlurlClient(string url) : this(new Url(url), false) { }
public FlurlClient() : this((Url)null, false) { }
/// <summary>
/// Creates a copy of this FlurlClient with a shared instance of HttpClient and HttpMessageHandler
/// </summary>
/// <returns></returns>
public FlurlClient Clone() {
return new FlurlClient {
_httpClient = _httpClient,
_httpMessageHandler = _httpMessageHandler,
Settings = Settings,
Url = Url,
AutoDispose = AutoDispose
};
}
private HttpClient _httpClient;
private HttpMessageHandler _httpMessageHandler;
@ -92,12 +106,8 @@ namespace Flurl.Http
/// This FlurlClient can still be reused, but those underlying objects will be re-created as needed. Previously set headers, etc, will be lost.
/// </summary>
public void Dispose() {
if (_httpMessageHandler != null)
_httpMessageHandler.Dispose();
if (_httpClient != null)
_httpClient.Dispose();
_httpMessageHandler?.Dispose();
_httpClient?.Dispose();
_httpMessageHandler = null;
_httpClient = null;
}

View File

@ -1,130 +1,161 @@
using System;
using System.Collections.Generic;
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 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));
Assert.AreEqual(client.HttpClient.Timeout, TimeSpan.FromSeconds(15));
}
[Test]
public void can_set_timeout_in_seconds() {
var client = "http://www.api.com".WithTimeout(15);
Assert.AreEqual(client.HttpClient.Timeout, TimeSpan.FromSeconds(15));
}
[Test]
public void can_set_header() {
var client = "http://www.api.com".WithHeader("a", 1);
var headers = client.HttpClient.DefaultRequestHeaders.ToList();
Assert.AreEqual(1, headers.Count);
Assert.AreEqual("a", headers[0].Key);
CollectionAssert.AreEqual(headers[0].Value, new[] { "1" });
}
[Test]
public void can_set_headers_from_anon_object() {
var client = "http://www.api.com".WithHeaders(new { a = "b", one = 2 });
var headers = client.HttpClient.DefaultRequestHeaders.ToList();
Assert.AreEqual(2, headers.Count);
Assert.AreEqual("a", headers[0].Key);
CollectionAssert.AreEqual(headers[0].Value, new[] { "b" });
Assert.AreEqual("one", headers[1].Key);
CollectionAssert.AreEqual(headers[1].Value, new[] { "2" });
}
[Test]
public void can_set_headers_from_dictionary() {
var client = "http://www.api.com".WithHeaders(new Dictionary<string, object> { { "a", "b" }, { "one", 2 } });
var headers = client.HttpClient.DefaultRequestHeaders.ToList();
Assert.AreEqual(2, headers.Count);
Assert.AreEqual("a", headers[0].Key);
CollectionAssert.AreEqual(headers[0].Value, new[] { "b" });
Assert.AreEqual("one", headers[1].Key);
CollectionAssert.AreEqual(headers[1].Value, new[] { "2" });
}
[Test]
public void can_setup_basic_auth() {
var client = "http://www.api.com".WithBasicAuth("user", "pass");
var header = client.HttpClient.DefaultRequestHeaders.First();
Assert.AreEqual("Authorization", header.Key);
Assert.AreEqual("Basic dXNlcjpwYXNz", header.Value.First());
}
[Test]
public void can_setup_oauth_bearer_token() {
var client = "http://www.api.com".WithOAuthBearerToken("mytoken");
var header = client.HttpClient.DefaultRequestHeaders.First();
Assert.AreEqual("Authorization", header.Key);
Assert.AreEqual("Bearer mytoken", header.Value.First());
}
[Test]
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");
// 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_any_http_status() {
using (var test = new HttpTest()) {
test.RespondWith(500, "epic fail");
try {
var result = await "http://www.api.com".AllowAnyHttpStatus().GetAsync();
Assert.IsFalse(result.IsSuccessStatusCode);
}
catch (Exception) {
Assert.Fail("Exception should not have been thrown.");
}
}
}
[Test, ExpectedException(typeof(FlurlHttpException))]
public async Task can_override_settings_fluently() {
using (var test = new HttpTest()) {
FlurlHttp.GlobalSettings.AllowedHttpStatusRange = "*";
test.RespondWith(500, "epic fail");
await "http://www.api.com".ConfigureClient(c => c.AllowedHttpStatusRange = "2xx").GetAsync();
}
}
}
}
using System;
using System.Collections.Generic;
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 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));
Assert.AreEqual(client.HttpClient.Timeout, TimeSpan.FromSeconds(15));
}
[Test]
public void can_set_timeout_in_seconds() {
var client = "http://www.api.com".WithTimeout(15);
Assert.AreEqual(client.HttpClient.Timeout, TimeSpan.FromSeconds(15));
}
[Test]
public void can_set_header() {
var client = "http://www.api.com".WithHeader("a", 1);
var headers = client.HttpClient.DefaultRequestHeaders.ToList();
Assert.AreEqual(1, headers.Count);
Assert.AreEqual("a", headers[0].Key);
CollectionAssert.AreEqual(headers[0].Value, new[] { "1" });
}
[Test]
public void can_set_headers_from_anon_object() {
var client = "http://www.api.com".WithHeaders(new { a = "b", one = 2 });
var headers = client.HttpClient.DefaultRequestHeaders.ToList();
Assert.AreEqual(2, headers.Count);
Assert.AreEqual("a", headers[0].Key);
CollectionAssert.AreEqual(headers[0].Value, new[] { "b" });
Assert.AreEqual("one", headers[1].Key);
CollectionAssert.AreEqual(headers[1].Value, new[] { "2" });
}
[Test]
public void can_set_headers_from_dictionary() {
var client = "http://www.api.com".WithHeaders(new Dictionary<string, object> { { "a", "b" }, { "one", 2 } });
var headers = client.HttpClient.DefaultRequestHeaders.ToList();
Assert.AreEqual(2, headers.Count);
Assert.AreEqual("a", headers[0].Key);
CollectionAssert.AreEqual(headers[0].Value, new[] { "b" });
Assert.AreEqual("one", headers[1].Key);
CollectionAssert.AreEqual(headers[1].Value, new[] { "2" });
}
[Test]
public void can_setup_basic_auth() {
var client = "http://www.api.com".WithBasicAuth("user", "pass");
var header = client.HttpClient.DefaultRequestHeaders.First();
Assert.AreEqual("Authorization", header.Key);
Assert.AreEqual("Basic dXNlcjpwYXNz", header.Value.First());
}
[Test]
public void can_setup_oauth_bearer_token() {
var client = "http://www.api.com".WithOAuthBearerToken("mytoken");
var header = client.HttpClient.DefaultRequestHeaders.First();
Assert.AreEqual("Authorization", header.Key);
Assert.AreEqual("Bearer mytoken", header.Value.First());
}
[Test]
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");
// 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_any_http_status() {
using (var test = new HttpTest()) {
test.RespondWith(500, "epic fail");
try {
var result = await "http://www.api.com".AllowAnyHttpStatus().GetAsync();
Assert.IsFalse(result.IsSuccessStatusCode);
}
catch (Exception) {
Assert.Fail("Exception should not have been thrown.");
}
}
}
[Test, ExpectedException(typeof(FlurlHttpException))]
public async Task can_override_settings_fluently() {
using (var test = new HttpTest()) {
FlurlHttp.GlobalSettings.AllowedHttpStatusRange = "*";
test.RespondWith(500, "epic fail");
await "http://www.api.com".ConfigureClient(c => c.AllowedHttpStatusRange = "2xx").GetAsync();
}
}
[Test]
public async Task can_share_HttpClient_with_different_urls() {
var client1 = new FlurlClient("http://www.api.com/for-client1").WithCookie("mycookie", "123");
var client2 = client1.WithUrl("http://www.api.com/for-client2");
var client3 = client1.WithUrl("http://www.api.com/for-client3");
var client4 = client2.WithUrl("http://www.api.com/for-client4");
CollectionAssert.AreEquivalent(client1.GetCookies(), client2.GetCookies());
CollectionAssert.AreEquivalent(client1.GetCookies(), client3.GetCookies());
CollectionAssert.AreEquivalent(client1.GetCookies(), client4.GetCookies());
var urls = new[] { client1, client2, client3, client4 }.Select(c => c.Url.ToString());
CollectionAssert.AllItemsAreUnique(urls);
}
[Test]
public async Task disposing_FlurlClient_doesnt_dispose_shared_HttpClient() {
var client1 = new FlurlClient("http://www.api.com/for-client1").WithCookie("mycookie", "123");
var client2 = client1.WithUrl("http://www.api.com/for-client2");
var client3 = client1.WithUrl("http://www.api.com/for-client3");
var client4 = client2.WithUrl("http://www.api.com/for-client4");
client2.Dispose();
client3.Dispose();
CollectionAssert.IsEmpty(client2.GetCookies());
CollectionAssert.IsEmpty(client3.GetCookies());
CollectionAssert.IsNotEmpty(client1.GetCookies());
CollectionAssert.IsNotEmpty(client4.GetCookies());
}
}
}