Skip to content

Commit 2086399

Browse files
committed
Merge branch 'httpclientreview' into dev
2 parents 4c0f0b0 + aecb81a commit 2086399

9 files changed

Lines changed: 346 additions & 129 deletions

File tree

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Net.Http;
1+
using System.Net.Http;
52
using System.Threading.Tasks;
63

74
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
85
{
96
public interface IHttpClient
107
{
11-
HttpClient Inst { get; }
12-
Task<string> GetStringAsync(string uri);
13-
Task<HttpResponseMessage> PostAsync<T>(string uri, T item);
14-
Task<HttpResponseMessage> DeleteAsync(string uri);
8+
Task<string> GetStringAsync(string uri, string authorizationToken = null, string authorizationMethod = "Bearer");
9+
10+
Task<HttpResponseMessage> PostAsync<T>(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer");
11+
12+
Task<HttpResponseMessage> DeleteAsync(string uri, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer");
13+
14+
Task<HttpResponseMessage> PutAsync<T>(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer");
1515
}
1616
}

src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs

Lines changed: 109 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
using Polly.Wrap;
55
using System;
66
using System.Collections.Generic;
7+
using System.Linq;
78
using System.Net;
89
using System.Net.Http;
10+
using System.Net.Http.Headers;
911
using System.Threading.Tasks;
1012

1113
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
@@ -18,47 +20,134 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
1820
public class ResilientHttpClient : IHttpClient
1921
{
2022
private HttpClient _client;
21-
private PolicyWrap _policyWrapper;
23+
private readonly Dictionary<string, PolicyWrap> _policiesPerOrigin;
2224
private ILogger<ResilientHttpClient> _logger;
23-
public HttpClient Inst => _client;
25+
private readonly Func<string, IEnumerable<Policy>> _policyCreator;
26+
//public HttpClient Inst => _client;
2427

25-
public ResilientHttpClient(Policy[] policies, ILogger<ResilientHttpClient> logger)
28+
public ResilientHttpClient(Func<string, IEnumerable<Policy>> policyCreator, ILogger<ResilientHttpClient> logger)
2629
{
2730
_client = new HttpClient();
2831
_logger = logger;
32+
_policiesPerOrigin = new Dictionary<string, PolicyWrap>();
33+
_policyCreator = policyCreator;
34+
}
2935

30-
// Add Policies to be applied
31-
_policyWrapper = Policy.WrapAsync(policies);
32-
}
36+
private Task<T> HttpInvoker<T>(string origin, Func<Task<T>> action)
37+
{
38+
var normalizedOrigin = NormalizeOrigin(origin);
39+
40+
if (!_policiesPerOrigin.ContainsKey(normalizedOrigin))
41+
{
42+
var newWrapper = Policy.WrapAsync(_policyCreator(normalizedOrigin).ToArray());
43+
_policiesPerOrigin.Add(normalizedOrigin, newWrapper);
44+
}
45+
46+
var policyWrapper = _policiesPerOrigin[normalizedOrigin];
47+
48+
// Executes the action applying all
49+
// the policies defined in the wrapper
50+
return policyWrapper.ExecuteAsync(() => action());
51+
}
52+
53+
private static string NormalizeOrigin(string origin)
54+
{
55+
return origin?.Trim()?.ToLower();
56+
}
3357

34-
public Task<string> GetStringAsync(string uri) =>
35-
HttpInvoker(() =>
36-
_client.GetStringAsync(uri));
58+
public Task<string> GetStringAsync(string uri, string authorizationToken = null, string authorizationMethod = "Bearer")
59+
{
60+
var origin = GetOriginFromUri(uri);
61+
return HttpInvoker(origin, async () =>
62+
{
63+
var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri);
64+
65+
if (authorizationToken != null)
66+
{
67+
requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken);
68+
}
69+
70+
var response = await _client.SendAsync(requestMessage);
71+
72+
return await response.Content.ReadAsStringAsync();
73+
});
74+
}
75+
76+
private static string GetOriginFromUri(string uri)
77+
{
78+
var url = new Uri(uri);
79+
var origin = $"{url.Scheme}://{url.DnsSafeHost}:{url.Port}";
80+
return origin;
81+
}
82+
83+
private Task<HttpResponseMessage> DoPostPutAsync<T>(HttpMethod method, string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer")
84+
{
85+
if (method != HttpMethod.Post && method != HttpMethod.Put)
86+
{
87+
throw new ArgumentException("Value must be either post or put.", nameof(method));
88+
}
3789

38-
public Task<HttpResponseMessage> PostAsync<T>(string uri, T item) =>
3990
// a new StringContent must be created for each retry
4091
// as it is disposed after each call
41-
HttpInvoker(() =>
92+
var origin = GetOriginFromUri(uri);
93+
return HttpInvoker(origin, async () =>
4294
{
43-
var response = _client.PostAsync(uri, new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json"));
95+
var requestMessage = new HttpRequestMessage(method, uri);
96+
97+
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json");
98+
99+
if (authorizationToken != null)
100+
{
101+
requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken);
102+
}
103+
104+
if (requestId != null)
105+
{
106+
requestMessage.Headers.Add("x-requestid", requestId);
107+
}
108+
109+
var response = await _client.SendAsync(requestMessage);
110+
44111
// raise exception if HttpResponseCode 500
45112
// needed for circuit breaker to track fails
46-
if (response.Result.StatusCode == HttpStatusCode.InternalServerError)
113+
114+
if (response.StatusCode == HttpStatusCode.InternalServerError)
47115
{
48116
throw new HttpRequestException();
49117
}
50118

51119
return response;
52120
});
121+
}
53122

54-
public Task<HttpResponseMessage> DeleteAsync(string uri) =>
55-
HttpInvoker(() => _client.DeleteAsync(uri));
123+
public Task<HttpResponseMessage> PostAsync<T>(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer")
124+
{
125+
return DoPostPutAsync(HttpMethod.Post, uri, item, authorizationToken, requestId, authorizationMethod);
126+
}
127+
public Task<HttpResponseMessage> PutAsync<T>(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer")
128+
{
129+
return DoPostPutAsync(HttpMethod.Put, uri, item, authorizationToken, requestId, authorizationMethod);
130+
}
131+
public Task<HttpResponseMessage> DeleteAsync(string uri, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer")
132+
{
133+
var origin = GetOriginFromUri(uri);
134+
return HttpInvoker(origin, async () =>
135+
{
136+
var requestMessage = new HttpRequestMessage(HttpMethod.Delete, uri);
56137

138+
if (authorizationToken != null)
139+
{
140+
requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken);
141+
}
57142

58-
private Task<T> HttpInvoker<T>(Func<Task<T>> action) =>
59-
// Executes the action applying all
60-
// the policies defined in the wrapper
61-
_policyWrapper.ExecuteAsync(() => action());
62-
}
143+
if (requestId != null)
144+
{
145+
requestMessage.Headers.Add("x-requestid", requestId);
146+
}
63147

148+
return await _client.SendAsync(requestMessage);
149+
});
150+
}
151+
152+
}
64153
}
Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using Microsoft.Extensions.Logging;
22
using Newtonsoft.Json;
33
using System;
4+
using System.Net;
45
using System.Net.Http;
6+
using System.Net.Http.Headers;
57
using System.Threading.Tasks;
68

79
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
@@ -10,24 +12,90 @@ public class StandardHttpClient : IHttpClient
1012
{
1113
private HttpClient _client;
1214
private ILogger<StandardHttpClient> _logger;
13-
public HttpClient Inst => _client;
15+
1416
public StandardHttpClient(ILogger<StandardHttpClient> logger)
1517
{
1618
_client = new HttpClient();
1719
_logger = logger;
1820
}
19-
20-
public Task<string> GetStringAsync(string uri) =>
21-
_client.GetStringAsync(uri);
2221

23-
public Task<HttpResponseMessage> PostAsync<T>(string uri, T item)
22+
public async Task<string> GetStringAsync(string uri, string authorizationToken = null, string authorizationMethod = "Bearer")
2423
{
25-
var contentString = new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json");
26-
return _client.PostAsync(uri, contentString);
24+
var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri);
25+
26+
if (authorizationToken != null)
27+
{
28+
requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken);
29+
}
30+
31+
var response = await _client.SendAsync(requestMessage);
32+
33+
return await response.Content.ReadAsStringAsync();
2734
}
2835

29-
public Task<HttpResponseMessage> DeleteAsync(string uri) =>
30-
_client.DeleteAsync(uri);
36+
private async Task<HttpResponseMessage> DoPostPutAsync<T>(HttpMethod method, string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer")
37+
{
38+
if (method != HttpMethod.Post && method != HttpMethod.Put)
39+
{
40+
throw new ArgumentException("Value must be either post or put.", nameof(method));
41+
}
42+
43+
// a new StringContent must be created for each retry
44+
// as it is disposed after each call
45+
46+
var requestMessage = new HttpRequestMessage(HttpMethod.Post, uri);
47+
48+
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json");
49+
50+
if (authorizationToken != null)
51+
{
52+
requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken);
53+
}
54+
55+
if (requestId != null)
56+
{
57+
requestMessage.Headers.Add("x-requestid", requestId);
58+
}
59+
60+
var response = await _client.SendAsync(requestMessage);
61+
62+
// raise exception if HttpResponseCode 500
63+
// needed for circuit breaker to track fails
64+
65+
if (response.StatusCode == HttpStatusCode.InternalServerError)
66+
{
67+
throw new HttpRequestException();
68+
}
69+
70+
return response;
71+
}
72+
73+
74+
public async Task<HttpResponseMessage> PostAsync<T>(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer")
75+
{
76+
return await DoPostPutAsync(HttpMethod.Post, uri, item, authorizationToken, requestId, authorizationToken);
77+
}
78+
79+
public async Task<HttpResponseMessage> PutAsync<T>(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer")
80+
{
81+
return await DoPostPutAsync(HttpMethod.Put, uri, item, authorizationToken, requestId, authorizationToken);
82+
}
83+
public async Task<HttpResponseMessage> DeleteAsync(string uri, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer")
84+
{
85+
var requestMessage = new HttpRequestMessage(HttpMethod.Delete, uri);
86+
87+
if (authorizationToken != null)
88+
{
89+
requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken);
90+
}
91+
92+
if (requestId != null)
93+
{
94+
requestMessage.Headers.Add("x-requestid", requestId);
95+
}
96+
97+
return await _client.SendAsync(requestMessage);
98+
}
3199
}
32100
}
33101

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
namespace WebMVC.Infrastructure
2+
{
3+
public static class API
4+
{
5+
public static class Basket
6+
{
7+
public static string GetBasket(string baseUri, string basketId)
8+
{
9+
return $"{baseUri}/{basketId}";
10+
}
11+
12+
public static string UpdateBasket(string baseUri)
13+
{
14+
return baseUri;
15+
}
16+
17+
public static string CleanBasket(string baseUri, string basketId)
18+
{
19+
return $"{baseUri}/{basketId}";
20+
}
21+
}
22+
23+
public static class Order
24+
{
25+
public static string GetOrder(string baseUri, string orderId)
26+
{
27+
return $"{baseUri}/{orderId}";
28+
}
29+
30+
public static string GetAllMyOrders(string baseUri)
31+
{
32+
return baseUri;
33+
}
34+
35+
public static string AddNewOrder(string baseUri)
36+
{
37+
return $"{baseUri}/new";
38+
}
39+
}
40+
41+
public static class Catalog
42+
{
43+
public static string GetAllCatalogItems(string baseUri, int page, int take, int? brand, int? type)
44+
{
45+
var filterQs = "";
46+
47+
if (brand.HasValue || type.HasValue)
48+
{
49+
var brandQs = (brand.HasValue) ? brand.Value.ToString() : "null";
50+
var typeQs = (type.HasValue) ? type.Value.ToString() : "null";
51+
filterQs = $"/type/{typeQs}/brand/{brandQs}";
52+
}
53+
54+
return $"{baseUri}items{filterQs}?pageIndex={page}&pageSize={take}";
55+
}
56+
57+
public static string GetAllBrands(string baseUri)
58+
{
59+
return $"{baseUri}catalogBrands";
60+
}
61+
62+
public static string GetAllTypes(string baseUri)
63+
{
64+
return $"{baseUri}catalogTypes";
65+
}
66+
}
67+
}
68+
}

src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public ResilientHttpClientFactory(ILogger<ResilientHttpClient> logger)
1717
=>_logger = logger;
1818

1919
public ResilientHttpClient CreateResilientHttpClient()
20-
=> new ResilientHttpClient(CreatePolicies(), _logger);
20+
=> new ResilientHttpClient((origin) => CreatePolicies(), _logger);
2121

2222

2323
private Policy[] CreatePolicies()

0 commit comments

Comments
 (0)