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 Jan 28, 2019
1 parent 9ec4355 commit 740585c
Show file tree
Hide file tree
Showing 27 changed files with 672 additions and 588 deletions.
2 changes: 1 addition & 1 deletion src/Stripe.net/Entities/_base/StripeEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static IHasObject FromJson(string value)
/// <param name="value">The object to deserialize.</param>
/// <returns>The deserialized Stripe object from the JSON string.</returns>
public static T FromJson<T>(string value)
where T : StripeEntity
where T : IStripeEntity
{
return JsonConvert.DeserializeObject<T>(value, StripeConfiguration.SerializerSettings);
}
Expand Down
84 changes: 0 additions & 84 deletions src/Stripe.net/Infrastructure/Client.cs

This file was deleted.

22 changes: 0 additions & 22 deletions src/Stripe.net/Infrastructure/Extensions/ServiceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,6 @@ namespace Stripe.Infrastructure.Extensions

internal static class ServiceExtensions
{
/// <summary>Creates the full URL for a request.</summary>
/// <typeparam name="T">Entity type returned by the service.</typeparam>
/// <param name="service">The service sending the request.</param>
/// <param name="options">The request parameters.</param>
/// <param name="baseUrl">The base URL for the request.</param>
/// <param name="isListMethod">Whether the request is a list request or not.</param>
/// <returns>The full URL for the request.</returns>
public static string ApplyAllParameters<T>(this Service<T> service, BaseOptions options, string baseUrl, bool isListMethod = false)
where T : IStripeEntity
{
var expansions = service.Expansions(isListMethod);
if (options != null)
{
expansions.AddRange(options.Expand);
}

return FormEncoder.AppendQueries(
baseUrl,
options?.ToQueryString(includeExtraParams: true, includeExpandParams: false),
FormEncoder.EncodeList(expansions, "expand"));
}

/// <summary>
/// Returns the list of attributes to expand in requests sent by the service.
/// </summary>
Expand Down
73 changes: 57 additions & 16 deletions src/Stripe.net/Infrastructure/FormEncoding/FormEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ namespace Stripe.Infrastructure.FormEncoding
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using Newtonsoft.Json;
using Stripe.Infrastructure.Http;

/// <summary>
/// This class provides methods to serialize various objects with
Expand All @@ -24,6 +27,60 @@ public static string EncodeOptions(BaseOptions options)
return EncodeValue(options, null);
}

public static HttpContent EncodeOptionsContent(BaseOptions options)
{
if (options is MultipartOptions multipartOptions)
{
return EncodeOptionsMultipart(multipartOptions);
}

return new FormUrlEncodedUTF8Content(options);
}

public static MultipartFormDataContent EncodeOptionsMultipart(MultipartOptions options)
{
var content = new MultipartFormDataContent();

foreach (var property in options.GetType().GetRuntimeProperties())
{
// Skip properties not annotated with `[JsonProperty]`
var attribute = property.GetCustomAttribute<JsonPropertyAttribute>();
if (attribute == null)
{
continue;
}

var key = attribute.PropertyName;
var value = property.GetValue(options);

if (value is Stream stream)
{
string fileName = "blob";
#if NET45 || NETSTANDARD2_0
FileStream fileStream = stream as FileStream;
if ((fileStream != null) && (!string.IsNullOrEmpty(fileStream.Name)))
{
fileName = fileStream.Name;
}
#endif
var streamContent = new StreamContent(stream);

content.Add(streamContent, key, fileName);
}
else
{
var flatParams = FlattenParamsValue(value, key);

foreach (var flatParam in flatParams)
{
content.Add(new StringContent(flatParam.Value), flatParam.Key);
}
}
}

return content;
}

/// <summary>Creates the HTTP query string for a given dictionary.</summary>
/// <param name="dictionary">The dictionary for which to create the query string.</param>
/// <returns>The query string.</returns>
Expand Down Expand Up @@ -60,22 +117,6 @@ public static string JoinQueries(params string[] queries)
return string.Join("&", queries.Where(q => !string.IsNullOrEmpty(q)));
}

/// <summary>Append one or more query strings to a URL.</summary>
/// <param name="url">The base URL.</param>
/// <param name="queries">One or more query strings to be appended to the URL.</param>
/// <returns>The full URL with all the query strings.</returns>
public static string AppendQueries(string url, params string[] queries)
{
var fullQuery = JoinQueries(queries);

if (!string.IsNullOrEmpty(fullQuery))
{
url += "?" + fullQuery;
}

return url;
}

/// <summary>Creates the HTTP query string for a given value.</summary>
/// <param name="value">The value to encode.</param>
/// <param name="key">
Expand Down
27 changes: 27 additions & 0 deletions src/Stripe.net/Infrastructure/Http/FormUrlEncodedUTF8Content.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Stripe.Infrastructure.Http
{
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Stripe.Infrastructure.Extensions;

public class FormUrlEncodedUTF8Content : ByteArrayContent
{
public FormUrlEncodedUTF8Content(BaseOptions options)
: base(GetContentByteArray(options))
{
this.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
this.Headers.ContentType.CharSet = "utf-8";
}

private static byte[] GetContentByteArray(BaseOptions options)
{
if (options == null)
{
return new byte[0];
}

return Encoding.UTF8.GetBytes(options.ToQueryString());
}
}
}
25 changes: 25 additions & 0 deletions src/Stripe.net/Infrastructure/Http/HttpClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Stripe.Infrastructure.Http
{
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>The last request made by this client.</summary>
public Request LastRequest { get; protected set; }

/// <summary>The last response received by this client.</summary>
public Response 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<Response> MakeRequestAsync(
Request request,
CancellationToken cancellationToken = default(CancellationToken));
}
}
119 changes: 119 additions & 0 deletions src/Stripe.net/Infrastructure/Http/Request.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
namespace Stripe.Infrastructure.Http
{
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
{
/// <summary>Initializes a new instance of the <see cref="Request"/> 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(
HttpMethod method,
string path,
BaseOptions options,
RequestOptions requestOptions)
{
this.Method = method;

this.Uri = BuildUri(method, path, options, requestOptions);

this.AuthorizationHeader = new AuthenticationHeaderValue(
"Bearer",
requestOptions?.ApiKey ?? StripeConfiguration.ApiKey);

this.StripeHeaders = BuildStripeHeaders(requestOptions);

this.Content = BuildContent(method, options);
}

/// <summary>The HTTP method for the request (GET, POST or DELETE).</summary>
public HttpMethod Method { get; }

/// <summary>
/// The URL for the request. If this is a GET or DELETE request, the URL also includes
/// the parameters in the query string.
/// </summary>
public Uri Uri { get; }

/// <summary>The value of the <c>Authorization</c> header with the API key.</summary>
public AuthenticationHeaderValue AuthorizationHeader { get; }

/// <summary>
/// Dictionary containing Stripe custom headers (<c>Stripe-Version</c>,
/// <c>Stripe-Account</c>, <c>Idempotency-Key</c>...).
/// </summary>
public Dictionary<string, string> StripeHeaders { get; }

/// <summary>
/// The body of the request. For POST requests, this will be either a
/// <c>application/x-www-form-urlencoded</c> or a <c>multipart/form-data</c> encoded
/// payload. For non-POST requests, this will be <c>null</c>.
/// </summary>
public HttpContent Content { get; }

private static Uri BuildUri(
HttpMethod method,
string path,
BaseOptions options,
RequestOptions requestOptions)
{
var b = new StringBuilder();

b.Append(requestOptions?.BaseUrl ?? StripeConfiguration.ApiBase);
b.Append(path);

if (method != HttpMethod.Post)
{
var queryString = options?.ToQueryString();
if (!string.IsNullOrEmpty(queryString))
{
b.Append("?");
b.Append(queryString);
}
}

return new Uri(b.ToString());
}

private static Dictionary<string, string> BuildStripeHeaders(RequestOptions requestOptions)
{
var stripeHeaders = new Dictionary<string, string>
{
{ "Stripe-Version", requestOptions?.StripeVersion ?? StripeConfiguration.ApiVersion },
};

if (!string.IsNullOrEmpty(requestOptions?.StripeConnectAccountId))
{
stripeHeaders.Add("Stripe-Account", requestOptions.StripeConnectAccountId);
}

if (!string.IsNullOrEmpty(requestOptions?.IdempotencyKey))
{
stripeHeaders.Add("Idempotency-Key", requestOptions.IdempotencyKey);
}

return stripeHeaders;
}

private static HttpContent BuildContent(HttpMethod method, BaseOptions options)
{
if (method != HttpMethod.Post)
{
return null;
}

return FormEncoder.EncodeOptionsContent(options);
}
}
}
Loading

0 comments on commit 740585c

Please sign in to comment.