#488 Changes to HttpCall and FlurlHttpException

This commit is contained in:
Todd Menier 2019-12-27 12:00:23 -06:00
parent 806938fae2
commit 2c82817a09
15 changed files with 150 additions and 172 deletions

View File

@ -40,7 +40,7 @@ namespace Flurl.Test.Http
[Test]
public async Task can_catch_parsing_error() {
HttpTest.RespondWith("I'm not JSON!");
HttpTest.RespondWith("{ \"invalid JSON!");
try {
await "http://myapi.com".GetJsonAsync();
@ -48,7 +48,7 @@ namespace Flurl.Test.Http
}
catch (FlurlParsingException ex) {
Assert.AreEqual("Response could not be deserialized to JSON: GET http://myapi.com", ex.Message);
Assert.AreEqual("I'm not JSON!", await ex.GetResponseStringAsync());
Assert.AreEqual("{ \"invalid JSON!", await ex.Call.Response.GetStringAsync());
// will differ if you're using a different serializer (which you probably aren't):
Assert.IsInstanceOf<Newtonsoft.Json.JsonReaderException>(ex.InnerException);
}

View File

@ -105,10 +105,10 @@ namespace Flurl.Test.Http
Assert.Fail("FlurlHttpException was not thrown!");
}
catch (FlurlHttpException ex) {
Assert.AreEqual("http://api.com/", ex.Call.Request.RequestUri.AbsoluteUri);
Assert.AreEqual(HttpMethod.Get, ex.Call.Request.Method);
Assert.AreEqual(HttpStatusCode.InternalServerError, ex.Call.Response.StatusCode);
Assert.AreEqual("bad job", await ex.GetResponseStringAsync());
Assert.AreEqual("http://api.com/", ex.Call.HttpRequestMessage.RequestUri.AbsoluteUri);
Assert.AreEqual(HttpMethod.Get, ex.Call.HttpRequestMessage.Method);
Assert.AreEqual(500, ex.Call.Response.StatusCode);
Assert.AreEqual("bad job", await ex.Call.Response.GetStringAsync());
}
}
@ -120,7 +120,7 @@ namespace Flurl.Test.Http
await "http://api.com".GetStringAsync();
}
catch (FlurlHttpException ex) {
var error = await ex.GetResponseJsonAsync<TestError>();
var error = await ex.Call.Response.GetJsonAsync<TestError>();
Assert.IsNotNull(error);
Assert.AreEqual(999, error.code);
Assert.AreEqual("our server crashed", error.message);
@ -135,7 +135,7 @@ namespace Flurl.Test.Http
await "http://api.com".GetStringAsync();
}
catch (FlurlHttpException ex) {
var error = await ex.GetResponseJsonAsync(); // error is a dynamic this time
var error = await ex.Call.Response.GetJsonAsync(); // error is a dynamic this time
Assert.IsNotNull(error);
Assert.AreEqual(999, error.code);
Assert.AreEqual("our server crashed", error.message);

View File

@ -133,7 +133,7 @@ namespace Flurl.Test.Http
.WithHeader("CONTENT-LENGTH", 10) // header names are case-insensitive
.PostJsonAsync(new { foo = "bar" });
var h = test.CallLog[0].Request.Content.Headers;
var h = test.CallLog[0].HttpRequestMessage.Content.Headers;
Assert.AreEqual(new[] { "application/json-patch+json; utf-8" }, h.GetValues("Content-Type"));
Assert.AreEqual(new[] { "10" }, h.GetValues("Content-Length"));
}

View File

@ -19,8 +19,8 @@ namespace Flurl.Test.Http
await "http://www.api.com".GetAsync();
var lastCall = HttpTest.CallLog.Last();
Assert.AreEqual(HttpStatusCode.OK, lastCall.Response.StatusCode);
Assert.AreEqual("great job", await lastCall.Response.Content.ReadAsStringAsync());
Assert.AreEqual(200, lastCall.Response.StatusCode);
Assert.AreEqual("great job", await lastCall.Response.GetStringAsync());
}
[Test]
@ -31,8 +31,8 @@ namespace Flurl.Test.Http
var lastCall = HttpTest.CallLog.Last();
Assert.AreEqual("{\"x\":5}", lastCall.RequestBody);
Assert.AreEqual(HttpStatusCode.OK, lastCall.Response.StatusCode);
Assert.AreEqual("great job", await lastCall.Response.Content.ReadAsStringAsync());
Assert.AreEqual(200, lastCall.Response.StatusCode);
Assert.AreEqual("great job", await lastCall.Response.GetStringAsync());
}
[Test]
@ -40,8 +40,8 @@ namespace Flurl.Test.Http
await "http://www.api.com".GetAsync();
var lastCall = HttpTest.CallLog.Last();
Assert.AreEqual(HttpStatusCode.OK, lastCall.Response.StatusCode);
Assert.AreEqual("", await lastCall.Response.Content.ReadAsStringAsync());
Assert.AreEqual(200, lastCall.Response.StatusCode);
Assert.AreEqual("", await lastCall.Response.GetStringAsync());
}
[Test]
@ -59,9 +59,9 @@ namespace Flurl.Test.Http
var calls = HttpTest.CallLog;
Assert.AreEqual(3, calls.Count);
Assert.AreEqual("one", await calls[0].Response.Content.ReadAsStringAsync());
Assert.AreEqual("two", await calls[1].Response.Content.ReadAsStringAsync());
Assert.AreEqual("three", await calls[2].Response.Content.ReadAsStringAsync());
Assert.AreEqual("one", await calls[0].Response.GetStringAsync());
Assert.AreEqual("two", await calls[1].Response.GetStringAsync());
Assert.AreEqual("three", await calls[2].Response.GetStringAsync());
HttpTest.ShouldHaveMadeACall();
HttpTest.ShouldHaveCalled("http://www.api.com/*").WithVerb(HttpMethod.Get).Times(3);
@ -175,8 +175,8 @@ namespace Flurl.Test.Http
public async Task can_respond_based_on_any_call_condition() {
HttpTest
.ForCallsTo("*")
.With(call => call.FlurlRequest.Url.Fragment.StartsWith("abc"))
.Without(call => call.FlurlRequest.Url.Fragment.EndsWith("xyz"))
.With(call => call.Request.Url.Fragment.StartsWith("abc"))
.Without(call => call.Request.Url.Fragment.EndsWith("xyz"))
.RespondWith("arbitrary conditions met!");
Assert.AreEqual("", await "http://api.com#abcxyz".GetStringAsync());
@ -425,7 +425,8 @@ namespace Flurl.Test.Http
public async Task can_fake_content_headers() {
HttpTest.RespondWith("<xml></xml>", 200, new { Content_Type = "text/xml" });
await "http://api.com".GetAsync();
HttpTest.ShouldHaveMadeACall().With(call => call.Response.Content.Headers.ContentType.MediaType == "text/xml");
HttpTest.ShouldHaveMadeACall().With(call => call.Response.Headers.Any(kv => kv.Key == "Content-Type" && kv.Value == "text/xml"));
HttpTest.ShouldHaveMadeACall().With(call => call.HttpResponseMessage.Content.Headers.ContentType.MediaType == "text/xml");
}
[Test] // #335

View File

@ -78,32 +78,32 @@ namespace Flurl.Http.Configuration
/// <summary>
/// Gets or sets a callback that is called immediately before every HTTP request is sent.
/// </summary>
public Action<HttpCall> BeforeCall {
get => Get<Action<HttpCall>>();
public Action<FlurlCall> BeforeCall {
get => Get<Action<FlurlCall>>();
set => Set(value);
}
/// <summary>
/// Gets or sets a callback that is asynchronously called immediately before every HTTP request is sent.
/// </summary>
public Func<HttpCall, Task> BeforeCallAsync {
get => Get<Func<HttpCall, Task>>();
public Func<FlurlCall, Task> BeforeCallAsync {
get => Get<Func<FlurlCall, Task>>();
set => Set(value);
}
/// <summary>
/// Gets or sets a callback that is called immediately after every HTTP response is received.
/// </summary>
public Action<HttpCall> AfterCall {
get => Get<Action<HttpCall>>();
public Action<FlurlCall> AfterCall {
get => Get<Action<FlurlCall>>();
set => Set(value);
}
/// <summary>
/// Gets or sets a callback that is asynchronously called immediately after every HTTP response is received.
/// </summary>
public Func<HttpCall, Task> AfterCallAsync {
get => Get<Func<HttpCall, Task>>();
public Func<FlurlCall, Task> AfterCallAsync {
get => Get<Func<FlurlCall, Task>>();
set => Set(value);
}
@ -111,8 +111,8 @@ namespace Flurl.Http.Configuration
/// Gets or sets a callback that is called when an error occurs during any HTTP call, including when any non-success
/// HTTP status code is returned in the response. Response should be null-checked if used in the event handler.
/// </summary>
public Action<HttpCall> OnError {
get => Get<Action<HttpCall>>();
public Action<FlurlCall> OnError {
get => Get<Action<FlurlCall>>();
set => Set(value);
}
@ -120,8 +120,8 @@ namespace Flurl.Http.Configuration
/// Gets or sets a callback that is asynchronously called when an error occurs during any HTTP call, including when any non-success
/// HTTP status code is returned in the response. Response should be null-checked if used in the event handler.
/// </summary>
public Func<HttpCall, Task> OnErrorAsync {
get => Get<Func<HttpCall, Task>>();
public Func<FlurlCall, Task> OnErrorAsync {
get => Get<Func<FlurlCall, Task>>();
set => Set(value);
}

View File

@ -45,7 +45,7 @@ namespace Flurl.Http.Configuration
/// <param name="stream">The stream.</param>
/// <returns></returns>
public T Deserialize<T>(Stream stream) {
// http://james.newtonking.com/json/help/index.html?topic=html/Performance.htm
// https://www.newtonsoft.com/json/help/html/Performance.htm#MemoryUsage
using (var sr = new StreamReader(stream))
using (var jr = new JsonTextReader(sr)) {
return JsonSerializer.CreateDefault(_settings).Deserialize<T>(jr);

View File

@ -9,32 +9,32 @@ namespace Flurl.Http
/// Encapsulates request, response, and other details associated with an HTTP call. Useful for diagnostics and available in
/// global event handlers and FlurlHttpException.Call.
/// </summary>
public class HttpCall
public class FlurlCall
{
/// <summary>
/// The IFlurlRequest associated with this call.
/// </summary>
public IFlurlRequest FlurlRequest { get; set; }
public IFlurlRequest Request { get; set; }
/// <summary>
/// The HttpRequestMessage associated with this call.
/// The raw HttpRequestMessage associated with this call.
/// </summary>
public HttpRequestMessage Request { get; set; }
public HttpRequestMessage HttpRequestMessage { get; set; }
/// <summary>
/// Captured request body. Available ONLY if Request.Content is a Flurl.Http.Content.CapturedStringContent.
/// Captured request body. Available ONLY if HttpRequestMessage.Content is a Flurl.Http.Content.CapturedStringContent.
/// </summary>
public string RequestBody => (Request.Content as CapturedStringContent)?.Content;
public string RequestBody => (HttpRequestMessage.Content as CapturedStringContent)?.Content;
/// <summary>
/// The IFlurlResponse associated with this call if the call completed, otherwise null.
/// </summary>
public IFlurlResponse FlurlResponse { get; set; }
public IFlurlResponse Response { get; set; }
/// <summary>
/// HttpResponseMessage associated with the call if the call completed, otherwise null.
/// The raw HttpResponseMessage associated with the call if the call completed, otherwise null.
/// </summary>
public HttpResponseMessage Response { get; set; }
public HttpResponseMessage HttpResponseMessage { get; set; }
/// <summary>
/// Exception that occurred while sending the HttpRequestMessage.
@ -65,25 +65,20 @@ namespace Flurl.Http
/// <summary>
/// True if a response was received, regardless of whether it is an error status.
/// </summary>
public bool Completed => Response != null;
public bool Completed => HttpResponseMessage != null;
/// <summary>
/// True if a response with a successful HTTP status was received.
/// </summary>
public bool Succeeded => Completed &&
(Response.IsSuccessStatusCode || HttpStatusRangeParser.IsMatch(FlurlRequest.Settings.AllowedHttpStatusRange, Response.StatusCode));
/// <summary>
/// HttpStatusCode of the response if the call completed, otherwise null.
/// </summary>
public HttpStatusCode? HttpStatus => Completed ? (HttpStatusCode?)Response.StatusCode : null;
(HttpResponseMessage.IsSuccessStatusCode || HttpStatusRangeParser.IsMatch(Request.Settings.AllowedHttpStatusRange, HttpResponseMessage.StatusCode));
/// <summary>
/// Returns the verb and absolute URI associated with this call.
/// </summary>
/// <returns></returns>
public override string ToString() {
return $"{Request.Method:U} {FlurlRequest.Url}";
return $"{HttpRequestMessage.Method:U} {Request.Url}";
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Dynamic;
using System.Threading.Tasks;
namespace Flurl.Http
@ -10,12 +9,10 @@ namespace Flurl.Http
/// </summary>
public class FlurlHttpException : Exception
{
private readonly string _capturedResponseBody;
/// <summary>
/// An object containing details about the failed HTTP call
/// </summary>
public HttpCall Call { get; }
public FlurlCall Call { get; }
/// <summary>
/// Initializes a new instance of the <see cref="FlurlHttpException"/> class.
@ -23,18 +20,8 @@ namespace Flurl.Http
/// <param name="call">The call.</param>
/// <param name="message">The message.</param>
/// <param name="inner">The inner.</param>
public FlurlHttpException(HttpCall call, string message, Exception inner) : this(call, message, null, inner) { }
/// <summary>
/// Initializes a new instance of the <see cref="FlurlHttpException"/> class.
/// </summary>
/// <param name="call">The call.</param>
/// <param name="message">The message.</param>
/// <param name="capturedResponseBody">The captured response body, if available.</param>
/// <param name="inner">The inner.</param>
public FlurlHttpException(HttpCall call, string message, string capturedResponseBody, Exception inner) : base(message, inner) {
public FlurlHttpException(FlurlCall call, string message, Exception inner) : base(message, inner) {
Call = call;
_capturedResponseBody = capturedResponseBody;
}
/// <summary>
@ -42,53 +29,20 @@ namespace Flurl.Http
/// </summary>
/// <param name="call">The call.</param>
/// <param name="inner">The inner.</param>
public FlurlHttpException(HttpCall call, Exception inner) : this(call, BuildMessage(call, inner), inner) { }
public FlurlHttpException(FlurlCall call, Exception inner) : this(call, BuildMessage(call, inner), inner) { }
/// <summary>
/// Initializes a new instance of the <see cref="FlurlHttpException"/> class.
/// </summary>
/// <param name="call">The call.</param>
public FlurlHttpException(HttpCall call) : this(call, BuildMessage(call, null), null) { }
public FlurlHttpException(FlurlCall call) : this(call, BuildMessage(call, null), null) { }
private static string BuildMessage(HttpCall call, Exception inner) {
private static string BuildMessage(FlurlCall call, Exception inner) {
return
(call.Response != null && !call.Succeeded) ?
$"Call failed with status code {(int)call.Response.StatusCode} ({call.Response.ReasonPhrase}): {call}":
$"Call failed with status code {call.Response.StatusCode} ({call.HttpResponseMessage.ReasonPhrase}): {call}":
$"Call failed. {inner?.Message} {call}";
}
/// <summary>
/// Gets the response body of the failed call.
/// </summary>
/// <returns>A task whose result is the string contents of the response body.</returns>
public Task<string> GetResponseStringAsync() {
if (_capturedResponseBody != null) return Task.FromResult(_capturedResponseBody);
var task = Call?.Response?.Content?.ReadAsStringAsync();
return task ?? Task.FromResult((string)null);
}
/// <summary>
/// Deserializes the JSON response body to an object of the given type.
/// </summary>
/// <typeparam name="T">A type whose structure matches the expected JSON response.</typeparam>
/// <returns>A task whose result is an object containing data in the response body.</returns>
public async Task<T> GetResponseJsonAsync<T>() {
var ser = Call.FlurlRequest?.Settings?.JsonSerializer;
if (ser == null) return default(T);
if (_capturedResponseBody != null)
return ser.Deserialize<T>(_capturedResponseBody);
var task = Call?.Response?.Content?.ReadAsStreamAsync();
if (task == null) return default(T);
return ser.Deserialize<T>(await task.ConfigureAwait(false));
}
/// <summary>
/// Deserializes the JSON response body to a dynamic object.
/// </summary>
/// <returns>A task whose result is an object containing data in the response body.</returns>
public async Task<dynamic> GetResponseJsonAsync() => await GetResponseJsonAsync<ExpandoObject>().ConfigureAwait(false);
}
/// <summary>
@ -99,11 +53,11 @@ namespace Flurl.Http
/// <summary>
/// Initializes a new instance of the <see cref="FlurlHttpTimeoutException"/> class.
/// </summary>
/// <param name="call">The HttpCall instance.</param>
/// <param name="call">Details of the HTTP call that caused the exception.</param>
/// <param name="inner">The inner exception.</param>
public FlurlHttpTimeoutException(HttpCall call, Exception inner) : base(call, BuildMessage(call), inner) { }
public FlurlHttpTimeoutException(FlurlCall call, Exception inner) : base(call, BuildMessage(call), inner) { }
private static string BuildMessage(HttpCall call) {
private static string BuildMessage(FlurlCall call) {
return $"Call timed out: {call}";
}
}
@ -116,11 +70,10 @@ namespace Flurl.Http
/// <summary>
/// Initializes a new instance of the <see cref="Flurl.Http.FlurlParsingException"/> class.
/// </summary>
/// <param name="call">The HttpCall instance.</param>
/// <param name="call">Details of the HTTP call that caused the exception.</param>
/// <param name="expectedFormat">The format that could not be parsed to, i.e. JSON.</param>
/// <param name="responseBody">The response body.</param>
/// <param name="inner">The inner exception.</param>
public FlurlParsingException(HttpCall call, string expectedFormat, string responseBody, Exception inner) : base(call, BuildMessage(call, expectedFormat), responseBody, inner) {
public FlurlParsingException(FlurlCall call, string expectedFormat, Exception inner) : base(call, BuildMessage(call, expectedFormat), inner) {
ExpectedFormat = expectedFormat;
}
@ -129,7 +82,7 @@ namespace Flurl.Http
/// </summary>
public string ExpectedFormat { get; }
private static string BuildMessage(HttpCall call, string expectedFormat) {
private static string BuildMessage(FlurlCall call, string expectedFormat) {
return $"Response could not be deserialized to {expectedFormat}: {call}";
}
}

View File

@ -111,7 +111,7 @@ namespace Flurl.Http
_client = Client; // "freeze" the client at this point to avoid excessive calls to FlurlClientFactory.Get (#374)
var request = new HttpRequestMessage(verb, Url) { Content = content };
var call = new HttpCall { FlurlRequest = this, Request = request };
var call = new FlurlCall { Request = this, HttpRequestMessage = request };
request.SetHttpCall(call);
await HandleEventAsync(Settings.BeforeCall, Settings.BeforeCallAsync, call).ConfigureAwait(false);
@ -135,12 +135,12 @@ namespace Flurl.Http
if (Settings.CookiesEnabled)
WriteRequestCookies(request);
call.Response = await Client.HttpClient.SendAsync(request, completionOption, cancellationTokenWithTimeout).ConfigureAwait(false);
call.Response.RequestMessage = request;
call.FlurlResponse = new FlurlResponse(call.Response);
call.HttpResponseMessage = await Client.HttpClient.SendAsync(request, completionOption, cancellationTokenWithTimeout).ConfigureAwait(false);
call.HttpResponseMessage.RequestMessage = request;
call.Response = new FlurlResponse(call.HttpResponseMessage);
if (call.Succeeded)
return call.FlurlResponse;
return call.Response;
throw new FlurlHttpException(call, null);
}
@ -152,7 +152,7 @@ namespace Flurl.Http
timeoutTokenSource?.Dispose();
if (Settings.CookiesEnabled)
ReadResponseCookies(call.Response);
ReadResponseCookies(call.HttpResponseMessage);
call.EndedUtc = DateTime.UtcNow;
await HandleEventAsync(Settings.AfterCall, Settings.AfterCallAsync, call).ConfigureAwait(false);
@ -211,12 +211,12 @@ namespace Flurl.Http
return null;
}
internal static async Task<IFlurlResponse> HandleExceptionAsync(HttpCall call, Exception ex, CancellationToken token) {
internal static async Task<IFlurlResponse> HandleExceptionAsync(FlurlCall call, Exception ex, CancellationToken token) {
call.Exception = ex;
await HandleEventAsync(call.FlurlRequest.Settings.OnError, call.FlurlRequest.Settings.OnErrorAsync, call).ConfigureAwait(false);
await HandleEventAsync(call.Request.Settings.OnError, call.Request.Settings.OnErrorAsync, call).ConfigureAwait(false);
if (call.ExceptionHandled)
return call.FlurlResponse;
return call.Response;
if (ex is OperationCanceledException && !token.IsCancellationRequested)
throw new FlurlHttpTimeoutException(call, ex);
@ -227,7 +227,7 @@ namespace Flurl.Http
throw new FlurlHttpException(call, ex);
}
private static Task HandleEventAsync(Action<HttpCall> syncHandler, Func<HttpCall, Task> asyncHandler, HttpCall call) {
private static Task HandleEventAsync(Action<FlurlCall> syncHandler, Func<FlurlCall, Task> asyncHandler, FlurlCall call) {
syncHandler?.Invoke(call);
if (asyncHandler != null)
return asyncHandler(call);

View File

@ -8,6 +8,7 @@ using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Flurl.Http.Configuration;
namespace Flurl.Http
{
@ -87,6 +88,9 @@ namespace Flurl.Http
public class FlurlResponse : IFlurlResponse
{
private readonly Lazy<IDictionary<string, string>> _headers;
private object _capturedBody = null;
private bool _streamRead = false;
private ISerializer _serializer = null;
/// <inheritdoc />
public IDictionary<string, string> Headers => _headers.Value;
@ -116,14 +120,22 @@ namespace Flurl.Http
/// <inheritdoc />
public async Task<T> GetJsonAsync<T>() {
if (_streamRead)
return _capturedBody is T body ? body : default(T);
var call = ResponseMessage.RequestMessage.GetHttpCall();
_serializer = call.Request.Settings.JsonSerializer;
using (var stream = await ResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false)) {
try {
return call.FlurlRequest.Settings.JsonSerializer.Deserialize<T>(stream);
_capturedBody = _serializer.Deserialize<T>(stream);
_streamRead = true;
return (T)_capturedBody;
}
catch (Exception ex) {
var body = await ResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
call.Exception = new FlurlParsingException(call, "JSON", body, ex);
_serializer = null;
_capturedBody = await ResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
_streamRead = true;
call.Exception = new FlurlParsingException(call, "JSON", ex);
await FlurlRequest.HandleExceptionAsync(call, call.Exception, CancellationToken.None).ConfigureAwait(false);
return default(T);
}
@ -143,22 +155,39 @@ namespace Flurl.Http
}
/// <inheritdoc />
public Task<string> GetStringAsync() {
public async Task<string> GetStringAsync() {
if (_streamRead) {
return
(_capturedBody == null) ? null :
// if GetJsonAsync<T> was called, we streamed the response directly to a T (for memory efficiency)
// without first capturing a string. it's too late to get it, so the best we can do is serialize the T
(_serializer != null) ? _serializer.Serialize(_capturedBody) :
_capturedBody?.ToString();
}
#if NETSTANDARD1_3 || NETSTANDARD2_0
// https://stackoverflow.com/questions/46119872/encoding-issues-with-net-core-2 (#86)
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
#endif
return ResponseMessage.Content.StripCharsetQuotes().ReadAsStringAsync();
_capturedBody = await ResponseMessage.Content.StripCharsetQuotes().ReadAsStringAsync();
_streamRead = true;
return (string)_capturedBody;
}
/// <inheritdoc />
public Task<Stream> GetStreamAsync() {
_streamRead = true;
return ResponseMessage.Content.ReadAsStreamAsync();
}
/// <inheritdoc />
public Task<byte[]> GetBytesAsync() {
return ResponseMessage.Content.ReadAsByteArrayAsync();
public async Task<byte[]> GetBytesAsync() {
if (_streamRead)
return _capturedBody as byte[];
_capturedBody = await ResponseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
_streamRead = true;
return (byte[])_capturedBody;
}
/// <summary>

View File

@ -36,18 +36,18 @@ namespace Flurl.Http
}
/// <summary>
/// Associate an HttpCall object with this request
/// Associate a FlurlCall object with this request
/// </summary>
internal static void SetHttpCall(this HttpRequestMessage request, HttpCall call) {
internal static void SetHttpCall(this HttpRequestMessage request, FlurlCall call) {
if (request?.Properties != null)
request.Properties["FlurlHttpCall"] = call;
}
/// <summary>
/// Get the HttpCall associated with this request, if any.
/// Get the FlurlCall associated with this request, if any.
/// </summary>
internal static HttpCall GetHttpCall(this HttpRequestMessage request) {
if (request?.Properties != null && request.Properties.TryGetValue("FlurlHttpCall", out var obj) && obj is HttpCall call)
internal static FlurlCall GetHttpCall(this HttpRequestMessage request) {
if (request?.Properties != null && request.Properties.TryGetValue("FlurlHttpCall", out var obj) && obj is FlurlCall call)
return call;
return null;
}

View File

@ -12,7 +12,7 @@ namespace Flurl.Http.Testing
/// </summary>
public class FilteredHttpTestSetup : HttpTestSetup
{
private readonly List<Func<HttpCall, bool>> _filters = new List<Func<HttpCall, bool>>();
private readonly List<Func<FlurlCall, bool>> _filters = new List<Func<FlurlCall, bool>>();
/// <summary>
/// Constructs a new instance of FilteredHttpTestSetup.
@ -21,18 +21,18 @@ namespace Flurl.Http.Testing
/// <param name="urlPatterns">URL(s) or URL pattern(s) that this HttpTestSetup applies to. Can contain * wildcard.</param>
public FilteredHttpTestSetup(TestFlurlHttpSettings settings, params string[] urlPatterns) : base(settings) {
if (urlPatterns.Any())
With(call => urlPatterns.Any(p => Util.MatchesPattern(call.FlurlRequest.Url, p)));
With(call => urlPatterns.Any(p => Util.MatchesPattern(call.Request.Url, p)));
}
/// <summary>
/// Returns true if the given HttpCall matches one of the URL patterns and all other criteria defined for this HttpTestSetup.
/// Returns true if the given FlurlCall matches one of the URL patterns and all other criteria defined for this HttpTestSetup.
/// </summary>
internal bool IsMatch(HttpCall call) => _filters.All(f => f(call));
internal bool IsMatch(FlurlCall call) => _filters.All(f => f(call));
/// <summary>
/// Defines a condition for which this HttpTestSetup applies.
/// </summary>
public FilteredHttpTestSetup With(Func<HttpCall, bool> condition) {
public FilteredHttpTestSetup With(Func<FlurlCall, bool> condition) {
_filters.Add(condition);
return this;
}
@ -40,7 +40,7 @@ namespace Flurl.Http.Testing
/// <summary>
/// Defines a condition for which this HttpTestSetup does NOT apply.
/// </summary>
public FilteredHttpTestSetup Without(Func<HttpCall, bool> condition) {
public FilteredHttpTestSetup Without(Func<FlurlCall, bool> condition) {
return With(c => !condition(c));
}

View File

@ -16,14 +16,14 @@ namespace Flurl.Http.Testing
private readonly bool _negate;
private readonly IList<string> _expectedConditions = new List<string>();
private IList<HttpCall> _calls;
private IList<FlurlCall> _calls;
/// <summary>
/// Constructs a new instance of HttpCallAssertion.
/// </summary>
/// <param name="loggedCalls">Set of calls (usually from HttpTest.CallLog) to assert against.</param>
/// <param name="negate">If true, assertions pass when calls matching criteria were NOT made.</param>
public HttpCallAssertion(IEnumerable<HttpCall> loggedCalls, bool negate = false) {
public HttpCallAssertion(IEnumerable<FlurlCall> loggedCalls, bool negate = false) {
_calls = loggedCalls.ToList();
_negate = negate;
}
@ -41,12 +41,12 @@ namespace Flurl.Http.Testing
Assert(expectedCount);
}
/// <summary>
/// Asserts whether calls were made matching the given predicate function.
/// </summary>
/// <param name="match">Predicate (usually a lambda expression) that tests an HttpCall and returns a bool.</param>
/// <param name="descrip">A description of what is being asserted.</param>
public HttpCallAssertion With(Func<HttpCall, bool> match, string descrip = null) {
/// <summary>
/// Asserts whether calls were made matching the given predicate function.
/// </summary>
/// <param name="match">Predicate (usually a lambda expression) that tests a FlurlCall and returns a bool.</param>
/// <param name="descrip">A description of what is being asserted.</param>
public HttpCallAssertion With(Func<FlurlCall, bool> match, string descrip = null) {
if (!string.IsNullOrEmpty(descrip))
_expectedConditions.Add(descrip);
_calls = _calls.Where(match).ToList();
@ -54,12 +54,12 @@ namespace Flurl.Http.Testing
return this;
}
/// <summary>
/// Asserts whether calls were made that do NOT match the given predicate function.
/// </summary>
/// <param name="match">Predicate (usually a lambda expression) that tests an HttpCall and returns a bool.</param>
/// <param name="descrip">A description of what is being asserted.</param>
public HttpCallAssertion Without(Func<HttpCall, bool> match, string descrip = null) {
/// <summary>
/// Asserts whether calls were made that do NOT match the given predicate function.
/// </summary>
/// <param name="match">Predicate (usually a lambda expression) that tests a FlurlCall and returns a bool.</param>
/// <param name="descrip">A description of what is being asserted.</param>
public HttpCallAssertion Without(Func<FlurlCall, bool> match, string descrip = null) {
return With(c => !match(c), descrip);
}
@ -68,7 +68,7 @@ namespace Flurl.Http.Testing
/// </summary>
/// <param name="urlPattern">Can contain * wildcard.</param>
public HttpCallAssertion WithUrlPattern(string urlPattern) {
return With(c => Util.MatchesPattern(c.FlurlRequest.Url, urlPattern), $"URL pattern {urlPattern}");
return With(c => Util.MatchesPattern(c.Request.Url, urlPattern), $"URL pattern {urlPattern}");
}
/// <summary>
@ -114,7 +114,7 @@ namespace Flurl.Http.Testing
/// </summary>
public HttpCallAssertion WithoutQueryParams(params string[] names) {
if (!names.Any())
return With(c => !c.FlurlRequest.Url.QueryParams.Any(), "no query parameters");
return With(c => !c.Request.Url.QueryParams.Any(), "no query parameters");
return names.Select(n => WithoutQueryParam(n)).LastOrDefault() ?? this;
}
@ -208,7 +208,7 @@ namespace Flurl.Http.Testing
/// </summary>
public HttpCallAssertion WithBasicAuth(string username = "*", string password = "*") {
return With(call => {
var val = call.Request.GetHeaderValue("Authorization");
var val = call.HttpRequestMessage.GetHeaderValue("Authorization");
if (val == null) return false;
if (!val.StartsWith("Basic ")) return false;
if ((username ?? "*") == "*" && (password ?? "*") == "*") return true;

View File

@ -17,7 +17,7 @@ namespace Flurl.Http.Testing
#endif
public class HttpTest : HttpTestSetup, IDisposable
{
private readonly ConcurrentQueue<HttpCall> _calls = new ConcurrentQueue<HttpCall>();
private readonly ConcurrentQueue<FlurlCall> _calls = new ConcurrentQueue<FlurlCall>();
private readonly List<FilteredHttpTestSetup> _filteredSetups = new List<FilteredHttpTestSetup>();
private readonly Lazy<HttpClient> _httpClient;
private readonly Lazy<HttpMessageHandler> _httpMessageHandler;
@ -34,7 +34,7 @@ namespace Flurl.Http.Testing
internal HttpClient HttpClient => _httpClient.Value;
internal HttpMessageHandler HttpMessageHandler => _httpMessageHandler.Value;
internal void LogCall(HttpCall call) => _calls.Enqueue(call);
internal void LogCall(FlurlCall call) => _calls.Enqueue(call);
/// <summary>
/// Gets the current HttpTest from the logical (async) call context
@ -44,7 +44,7 @@ namespace Flurl.Http.Testing
/// <summary>
/// List of all (fake) HTTP calls made since this HttpTest was created.
/// </summary>
public IReadOnlyList<HttpCall> CallLog => new ReadOnlyCollection<HttpCall>(_calls.ToList());
public IReadOnlyList<FlurlCall> CallLog => new ReadOnlyCollection<FlurlCall>(_calls.ToList());
/// <summary>
/// Change FlurlHttpSettings for the scope of this HttpTest.
@ -65,7 +65,7 @@ namespace Flurl.Http.Testing
return setup;
}
internal HttpTestSetup FindSetup(HttpCall call) {
internal HttpTestSetup FindSetup(FlurlCall call) {
return _filteredSetups.FirstOrDefault(ts => ts.IsMatch(call)) ?? (HttpTestSetup)this;
}

View File

@ -24,16 +24,16 @@ namespace Flurl.Http.Testing
return Regex.IsMatch(textToCheck ?? "", regex);
}
internal static bool HasAnyVerb(this HttpCall call, HttpMethod[] verbs) {
return verbs.Any(verb => call.Request.Method == verb);
internal static bool HasAnyVerb(this FlurlCall call, HttpMethod[] verbs) {
return verbs.Any(verb => call.HttpRequestMessage.Method == verb);
}
internal static bool HasAnyVerb(this HttpCall call, string[] verbs) {
return verbs.Any(verb => call.Request.Method.Method.Equals(verb, StringComparison.OrdinalIgnoreCase));
internal static bool HasAnyVerb(this FlurlCall call, string[] verbs) {
return verbs.Any(verb => call.HttpRequestMessage.Method.Method.Equals(verb, StringComparison.OrdinalIgnoreCase));
}
internal static bool HasQueryParam(this HttpCall call, string name, object value) {
var paramVals = call.FlurlRequest.Url.QueryParams
internal static bool HasQueryParam(this FlurlCall call, string name, object value) {
var paramVals = call.Request.Url.QueryParams
.Where(p => p.Name == name)
.Select(p => p.Value.ToInvariantString())
.ToList();
@ -51,27 +51,27 @@ namespace Flurl.Http.Testing
return paramVals.Any(v => v == value.ToInvariantString());
}
internal static bool HasAllQueryParams(this HttpCall call, string[] names) {
return call.FlurlRequest.Url.QueryParams
internal static bool HasAllQueryParams(this FlurlCall call, string[] names) {
return call.Request.Url.QueryParams
.Select(p => p.Name)
.Intersect(names)
.Count() == names.Length;
}
internal static bool HasAnyQueryParam(this HttpCall call, string[] names) {
var qp = call.FlurlRequest.Url.QueryParams;
internal static bool HasAnyQueryParam(this FlurlCall call, string[] names) {
var qp = call.Request.Url.QueryParams;
return names.Any() ? qp
.Select(p => p.Name)
.Intersect(names)
.Any() : qp.Any();
}
internal static bool HasQueryParams(this HttpCall call, object values) {
internal static bool HasQueryParams(this FlurlCall call, object values) {
return values.ToKeyValuePairs().All(kv => call.HasQueryParam(kv.Key, kv.Value));
}
internal static bool HasHeader(this HttpCall call, string name, string valuePattern) {
var val = call.Request.GetHeaderValue(name);
internal static bool HasHeader(this FlurlCall call, string name, string valuePattern) {
var val = call.HttpRequestMessage.GetHeaderValue(name);
return val != null && MatchesPattern(val, valuePattern);
}
}