Skip to content

Commit

Permalink
Revamp HTTP client requestor
Browse files Browse the repository at this point in the history
  • Loading branch information
ob-stripe committed Feb 10, 2019
1 parent 87a69b5 commit a6ffb68
Show file tree
Hide file tree
Showing 18 changed files with 626 additions and 246 deletions.
2 changes: 1 addition & 1 deletion src/Stripe.net/Entities/EphemeralKeys/EphemeralKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class EphemeralKey : StripeEntity<EphemeralKey>, IHasId, IHasObject
// that they'll be able to decode an object that's current according to their version.
public string RawJson
{
get { return this.StripeResponse?.ResponseJson; }
get { return this.StripeResponse?.Content; }
}

[JsonProperty("created")]
Expand Down
32 changes: 32 additions & 0 deletions src/Stripe.net/Infrastructure/Public/HttpClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Stripe
{
using System;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// Abstract base class for HTTP clients used to make requests to Stripe's API.
/// </summary>
public abstract class HttpClient
{
/// <summary>Gets or sets the last request made by this client.</summary>
/// <value>The last request made by this client.</value>
public StripeRequest LastRequest { get; protected set; }

/// <summary>Gets or sets the duration of the last request.</summary>
/// <value>The duration of the last request.</value>
public TimeSpan? LastRequestDuration { get; protected set; }

/// <summary>Gets or sets the last response received by this client.</summary>
/// <value>The last response received by this client.</value>
public StripeResponse LastResponse { get; protected set; }

/// <summary>Sends a request to Stripe's API as an asynchronous operation.</summary>
/// <param name="request">The parameters of the request to send.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>The task object representing the asynchronous operation.</returns>
public abstract Task<StripeResponse> MakeRequestAsync(
StripeRequest request,
CancellationToken cancellationToken = default(CancellationToken));
}
}
28 changes: 28 additions & 0 deletions src/Stripe.net/Infrastructure/Public/IStripeClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace Stripe
{
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// Interface for a Stripe client.
/// </summary>
public interface IStripeClient
{
/// <summary>Sends a request to Stripe's API as an asynchronous operation.</summary>
/// <typeparam name="T">Type of the Stripe entity returned by the API.</typeparam>
/// <param name="method">The HTTP method.</param>
/// <param name="path">The path of the request.</param>
/// <param name="options">The parameters of the request.</param>
/// <param name="requestOptions">The special modifiers of the request.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>The task object representing the asynchronous operation.</returns>
Task<T> RequestAsync<T>(
HttpMethod method,
string path,
BaseOptions options,
RequestOptions requestOptions,
CancellationToken cancellationToken = default(CancellationToken))
where T : IStripeEntity;
}
}
92 changes: 92 additions & 0 deletions src/Stripe.net/Infrastructure/Public/StripeClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
namespace Stripe
{
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;

/// <summary>
/// A Stripe client, used to issue requests to Stripe's API and deserialize responses.
/// </summary>
public class StripeClient : IStripeClient
{
/// <summary>Initializes a new instance of the <see cref="StripeClient"/> class.</summary>
/// <param name="httpClient">
/// The <see cref="Stripe.HttpClient"/> client to use. If <c>null</c>,
/// an HTTP client will be created with default parameters.
/// </param>
public StripeClient(Stripe.HttpClient httpClient = null)
{
this.HttpClient = httpClient ?? BuildDefaultHttpClient();
}

/// <summary>Gets the <see cref="Stripe.HttpClient"/> used to send HTTP requests.</summary>
/// <value>The <see cref="Stripe.HttpClient"/> used to send HTTP requests.</value>
public Stripe.HttpClient HttpClient { get; }

/// <summary>Sends a request to Stripe's API as an asynchronous operation.</summary>
/// <typeparam name="T">Type of the Stripe entity returned by the API.</typeparam>
/// <param name="method">The HTTP method.</param>
/// <param name="path">The path of the request.</param>
/// <param name="options">The parameters of the request.</param>
/// <param name="requestOptions">The special modifiers of the request.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>The task object representing the asynchronous operation.</returns>
public async Task<T> RequestAsync<T>(
HttpMethod method,
string path,
BaseOptions options,
RequestOptions requestOptions,
CancellationToken cancellationToken = default(CancellationToken))
where T : IStripeEntity
{
var request = new StripeRequest(method, path, options, requestOptions);

var response = await this.HttpClient.MakeRequestAsync(request);

return ProcessResponse<T>(response);
}

private static Stripe.HttpClient BuildDefaultHttpClient()
{
return new SystemNetHttpClient();
}

private static T ProcessResponse<T>(StripeResponse response)
where T : IStripeEntity
{
if (response.StatusCode != HttpStatusCode.OK)
{
throw BuildStripeException(response);
}

var obj = StripeEntity.FromJson<T>(response.Content);
obj.StripeResponse = response;

return obj;
}

private static StripeException BuildStripeException(StripeResponse response)
{
// If the value of the `error` key is a string, then the error is an OAuth error
// and we instantiate the StripeError object with the entire JSON.
// Otherwise, it's a regular API error and we instantiate the StripeError object
// with just the nested hash contained in the `error` key.
var errorToken = JObject.Parse(response.Content)["error"];
var stripeError = errorToken.Type == JTokenType.String
? StripeError.FromJson(response.Content)
: StripeError.FromJson(errorToken.ToString());

stripeError.StripeResponse = response;

return new StripeException(
response.StatusCode,
stripeError,
stripeError.Message)
{
StripeResponse = response,
};
}
}
}
51 changes: 32 additions & 19 deletions src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ namespace Stripe
{
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Reflection;
using Newtonsoft.Json;
using Stripe.Infrastructure;
Expand All @@ -14,9 +13,11 @@ public static class StripeConfiguration
{
private static string apiKey;

private static IStripeClient stripeClient;

static StripeConfiguration()
{
StripeNetVersion = new AssemblyName(typeof(Requestor).GetTypeInfo().Assembly.FullName).Version.ToString(3);
StripeNetVersion = new AssemblyName(typeof(StripeConfiguration).GetTypeInfo().Assembly.FullName).Version.ToString(3);
}

/// <summary>API version used by Stripe.net.</summary>
Expand Down Expand Up @@ -71,12 +72,6 @@ public static string ApiKey
/// <summary>Gets or sets the base URL for Stripe's Files API.</summary>
public static string FilesBase { get; set; } = DefaultFilesBase;

/// <summary>Gets or sets a custom <see cref="HttpMessageHandler"/>.</summary>
public static HttpMessageHandler HttpMessageHandler { get; set; }

/// <summary>Gets or sets the timespan to wait before the request times out.</summary>
public static TimeSpan HttpTimeout { get; set; } = DefaultHttpTimeout;

/// <summary>
/// Gets or sets the settings used for deserializing JSON objects returned by Stripe's API.
/// It is highly recommended you do not change these settings, as doing so can produce
Expand All @@ -87,22 +82,40 @@ public static string ApiKey
/// </summary>
public static JsonSerializerSettings SerializerSettings { get; set; } = DefaultSerializerSettings();

/// <summary>Gets the version of the Stripe.net client library.</summary>
public static string StripeNetVersion { get; }

/// <summary>
/// Gets or sets the timespan to wait before the request times out.
/// This property is deprecated and will be removed in a future version, please use the
/// <see cref="HttpTimeout"/> property instead.
/// Gets or sets a custom <see cref="StripeClient"/> for sending requests to Stripe's
/// API. You can use this to use a custom message handler, set proxy parameters, etc.
/// </summary>
// TODO: remove this property in a future major version
[Obsolete("Use StripeConfiguration.HttpTimeout instead.")]
public static TimeSpan? HttpTimeSpan
/// <example>
/// To use a custom message handler:
/// <code>
/// System.Net.Http.HttpMessageHandler messageHandler = ...;
/// var httpClient = new System.Net.HttpClient(messageHandler);
/// var stripeClient = new Stripe.StripeClient(new Stripe.SystemNetHttpClient(httpClient));
/// Stripe.StripeConfiguration.StripeClient = stripeClient;
/// </code>
/// </example>
public static IStripeClient StripeClient
{
get { return HttpTimeout; }
set { HttpTimeout = value ?? DefaultHttpTimeout; }
get
{
if (stripeClient == null)
{
stripeClient = new StripeClient();
}

return stripeClient;
}

set
{
stripeClient = value;
}
}

/// <summary>Gets the version of the Stripe.net client library.</summary>
public static string StripeNetVersion { get; }

/// <summary>
/// Returns a new instance of <see cref="Newtonsoft.Json.JsonSerializerSettings"/> with
/// the default settings used by Stripe.net.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
namespace Stripe.Infrastructure
namespace Stripe
{
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Stripe.Infrastructure.Extensions;
using Stripe.Infrastructure.FormEncoding;

/// <summary>
/// Represents a request to Stripe's API.
/// </summary>
public class Request
public class StripeRequest
{
/// <summary>Initializes a new instance of the <see cref="Request"/> class.</summary>
/// <summary>Initializes a new instance of the <see cref="StripeRequest"/> class.</summary>
/// <param name="method">The HTTP method.</param>
/// <param name="path">The path of the request.</param>
/// <param name="options">The parameters of the request.</param>
/// <param name="requestOptions">The special modifiers of the request.</param>
public Request(
public StripeRequest(
HttpMethod method,
string path,
BaseOptions options,
Expand Down Expand Up @@ -62,6 +61,17 @@ public Request(
/// </summary>
public HttpContent Content { get; }

/// <summary>Returns a string that represents the <see cref="StripeRequest"/>.</summary>
/// <returns>A string that represents the <see cref="StripeRequest"/>.</returns>
public override string ToString()
{
return string.Format(
"<{0} Method={1} Uri={2}>",
this.GetType().FullName,
this.Method,
this.Uri.ToString());
}

private static Uri BuildUri(
HttpMethod method,
string path,
Expand Down
83 changes: 80 additions & 3 deletions src/Stripe.net/Infrastructure/Public/StripeResponse.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,90 @@
namespace Stripe
{
using System;
using System.Linq;
using System.Net;
using System.Net.Http.Headers;

/// <summary>
/// Represents a response from Stripe's API.
/// </summary>
public class StripeResponse
{
public string ResponseJson { get; set; }
/// <summary>Initializes a new instance of the <see cref="StripeResponse"/> class.</summary>
/// <param name="statusCode">The HTTP status code.</param>
/// <param name="headers">The HTTP headers of the response.</param>
/// <param name="content">The body of the response.</param>
public StripeResponse(HttpStatusCode statusCode, HttpResponseHeaders headers, string content)
{
this.StatusCode = statusCode;
this.Headers = headers;
this.Content = content;
}

public string RequestId { get; set; }
/// <summary>Gets the HTTP status code of the response.</summary>
/// <value>The HTTP status code of the response.</value>
public HttpStatusCode StatusCode { get; }

public DateTime RequestDate { get; set; }
/// <summary>Gets the HTTP headers of the response.</summary>
/// <value>The HTTP headers of the response.</value>
public HttpResponseHeaders Headers { get; }

/// <summary>Gets the body of the response.</summary>
/// <value>The body of the response.</value>
public string Content { get; }

/// <summary>Gets the date of the request, as returned by Stripe.</summary>
/// <value>The date of the request, as returned by Stripe.</value>
public DateTimeOffset? Date => this.Headers?.Date;

/// <summary>Gets the idempotency key of the request, as returned by Stripe.</summary>
/// <value>The idempotency key of the request, as returned by Stripe.</value>
public string IdempotencyKey => MaybeGetHeader(this.Headers, "Idempotency-Key");

/// <summary>Gets the ID of the request, as returned by Stripe.</summary>
/// <value>The ID of the request, as returned by Stripe.</value>
public string RequestId => MaybeGetHeader(this.Headers, "Request-Id");

/// <summary>
/// Gets the body of the response.
/// This method is deprecated and will be removed in a future version, please use the
/// <see cref="Content"/> property getter instead.
/// </summary>
/// <value>The body of the response.</value>
// TODO: remove this in a future a major version
[Obsolete("Use Content instead")]
public string ResponseJson => this.Content;

/// <summary>
/// Gets the date of the request, as returned by Stripe.
/// This method is deprecated and will be removed in a future version, please use the
/// <see cref="Date"/> property getter instead.
/// </summary>
/// <value>The date of the request, as returned by Stripe.</value>
// TODO: remove this in a future a major version
[Obsolete("Use Date instead")]
public DateTime RequestDate => this.Date?.DateTime ?? default(DateTime);

/// <summary>Returns a string that represents the <see cref="StripeResponse"/>.</summary>
/// <returns>A string that represents the <see cref="StripeResponse"/>.</returns>
public override string ToString()
{
return string.Format(
"<{0} status={1} Request-Id={2} Date={3}>",
this.GetType().FullName,
(int)this.StatusCode,
this.RequestId,
this.Date?.ToString("s"));
}

private static string MaybeGetHeader(HttpHeaders headers, string name)
{
if ((headers == null) || (!headers.Contains(name)))
{
return null;
}

return headers.GetValues(name).First();
}
}
}
Loading

0 comments on commit a6ffb68

Please sign in to comment.