Skip to content

Commit 7f138cc

Browse files
committed
Merge pull request #526 from carolynvs/retry-on-token-expiration
Retry requests on 401 Unauthorized result
2 parents 9c53fa4 + 35b17a9 commit 7f138cc

File tree

13 files changed

+324
-70
lines changed

13 files changed

+324
-70
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System.Net.Http;
2+
using Flurl;
3+
using Flurl.Http;
4+
using Flurl.Http.Configuration;
5+
6+
namespace OpenStack.Authentication
7+
{
8+
/// <summary>
9+
/// Instructs Flurl to use our <see cref="AuthenticatedMessageHandler"/> for all requests.
10+
/// </summary>
11+
internal class AuthenticatedHttpClientFactory : DefaultHttpClientFactory
12+
{
13+
public override HttpClient CreateClient(Url url, HttpMessageHandler handler)
14+
{
15+
return new HttpClient(handler)
16+
{
17+
Timeout = FlurlHttp.Configuration.DefaultTimeout
18+
};
19+
}
20+
21+
public override HttpMessageHandler CreateMessageHandler()
22+
{
23+
return new AuthenticatedMessageHandler(base.CreateMessageHandler());
24+
}
25+
}
26+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.Net;
2+
using System.Net.Http;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Flurl.Http;
6+
using Flurl.Http.Configuration;
7+
8+
namespace OpenStack.Authentication
9+
{
10+
/// <summary>
11+
/// Used by Flurl for all requests. Understands how to authenticate and retry when a token expires.
12+
/// </summary>
13+
internal class AuthenticatedMessageHandler : FlurlMessageHandler
14+
{
15+
public AuthenticatedMessageHandler(HttpMessageHandler innerHandler)
16+
: base(innerHandler)
17+
{ }
18+
19+
public IAuthenticationProvider AuthenticationProvider;
20+
21+
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
22+
{
23+
string token = await AuthenticationProvider.GetToken(cancellationToken).ConfigureAwait(false);
24+
request.Headers.SetAuthToken(token);
25+
26+
try
27+
{
28+
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
29+
}
30+
catch (FlurlHttpException ex)
31+
{
32+
if (ex.Call.HttpStatus != HttpStatusCode.Unauthorized)
33+
throw;
34+
}
35+
36+
// Retry with a new token
37+
var retryRequest = request.Copy();
38+
var retryToken = await AuthenticationProvider.GetToken(cancellationToken).ConfigureAwait(false);
39+
retryRequest.Headers.SetAuthToken(retryToken);
40+
return await base.SendAsync(retryRequest, cancellationToken).ConfigureAwait(false);
41+
}
42+
}
43+
}

src/corelib/Authentication/ServiceUrlBuilder.cs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System.Threading;
22
using System.Threading.Tasks;
33
using Flurl;
4-
using Flurl.Http;
54

65
namespace OpenStack.Authentication
76
{
@@ -25,16 +24,5 @@ public async Task<Url> GetEndpoint(CancellationToken cancellationToken)
2524
string endpoint = await _authenticationProvider.GetEndpoint(_serviceType, _region, _useInternalUrl, cancellationToken).ConfigureAwait(false);
2625
return new Url(endpoint);
2726
}
28-
29-
public async Task<string> GetToken(CancellationToken cancellationToken)
30-
{
31-
return await _authenticationProvider.GetToken(cancellationToken).ConfigureAwait(false);
32-
}
33-
34-
public async Task<FlurlClient> Authenticate(FlurlClient client, CancellationToken cancellationToken)
35-
{
36-
string token = await GetToken(cancellationToken).ConfigureAwait(false);
37-
return client.Authenticate(token);
38-
}
3927
}
4028
}

src/corelib/Configuration.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System;
2+
using Flurl.Http;
3+
using Flurl.Http.Configuration;
4+
using Newtonsoft.Json;
5+
using OpenStack.Authentication;
6+
using OpenStack.Serialization;
7+
8+
namespace OpenStack
9+
{
10+
/// <summary>
11+
/// A static container for global configuration settings affecting OpenStack.NET behavior.
12+
/// </summary>
13+
/// <threadsafety static="true" instance="false"/>
14+
public static class OpenStackNet
15+
{
16+
private static readonly object ConfigureLock = new object();
17+
private static bool _isConfigured = false;
18+
19+
/// <summary>
20+
/// Provides thread-safe accesss to OpenStack.NET's global configuration options.
21+
/// <para>
22+
/// Can only be called once at application start-up, before instantiating any OpenStack.NET objects.
23+
/// </para>
24+
/// </summary>
25+
/// <param name="configureFlurl">Addtional configuration of Flurl's global settings <seealso cref="Flurl.Http.FlurlHttp.Configure"/>.</param>
26+
/// <param name="configureJson">Additional configuration of Json.NET's glboal settings <seealso cref="Newtonsoft.Json.JsonConvert.DefaultSettings"/>.</param>
27+
public static void Configure(Action<FlurlHttpConfigurationOptions> configureFlurl = null, Action<JsonSerializerSettings> configureJson = null)
28+
{
29+
if (_isConfigured)
30+
return;
31+
32+
lock (ConfigureLock)
33+
{
34+
if (_isConfigured)
35+
return;
36+
37+
JsonConvert.DefaultSettings = () =>
38+
{
39+
// Apply our default settings
40+
var settings = new JsonSerializerSettings
41+
{
42+
DefaultValueHandling = DefaultValueHandling.Ignore,
43+
NullValueHandling = NullValueHandling.Ignore,
44+
ContractResolver = new EmptyEnumerableResolver()
45+
};
46+
47+
// Apply application's default settings
48+
if (configureJson != null)
49+
configureJson(settings);
50+
return settings;
51+
};
52+
53+
FlurlHttp.Configure(c =>
54+
{
55+
// Apply our default settings
56+
c.HttpClientFactory = new AuthenticatedHttpClientFactory();
57+
58+
// Apply application's default settings
59+
if (configureFlurl != null)
60+
configureFlurl(c);
61+
});
62+
63+
_isConfigured = true;
64+
}
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)