#488 Changes to HttpCall and FlurlHttpException
This commit is contained in:
parent
806938fae2
commit
2c82817a09
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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"));
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
@ -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}";
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user