Skip to content

Commit d9bb6cc

Browse files
committed
Enable vendors to alter the SDK user agent header
The new way to set custom headers is to use OpenStackNet.Configure(x => x.AdditionalUserAgents.Add(new ProductInfoHeaderValue(...))); I switched to a list so that the application and any vender packages could set as many UserAgent headers as needed, e.g. openstack.net/x.x.x.x rackspace.net/x.x.x.x PoshStack/x.x.x.x I also tweaked the logic when configuring Flurl so that consumers don't accidently clobber our required configuration
1 parent 8936f13 commit d9bb6cc

8 files changed

Lines changed: 164 additions & 68 deletions

File tree

src/corelib/Core/UserAgentGenerator.cs

Lines changed: 0 additions & 37 deletions
This file was deleted.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System.Diagnostics;
2+
using System.Reflection;
3+
4+
namespace System.Extensions
5+
{
6+
/// <summary>
7+
/// Useful System.Type extension methods for custom implementations.
8+
/// </summary>
9+
/// <exclude />
10+
public static class TypeExtensions
11+
{
12+
/// <summary>
13+
/// Gets the AssemblyFileVersion for the specified type.
14+
/// </summary>
15+
/// <param name="type">The type which resides in the desired assembly.</param>
16+
public static string GetAssemblyFileVersion(this Type type)
17+
{
18+
Assembly assembly = type.Assembly;
19+
try
20+
{
21+
var fileVersionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
22+
return fileVersionInfo.FileVersion;
23+
}
24+
catch
25+
{
26+
return assembly.GetName().Version.ToString();
27+
}
28+
}
29+
}
30+
31+
}

src/corelib/OpenStack.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
<Compile Include="Authentication\NamespaceDoc.cs" />
8383
<Compile Include="Authentication\ServiceType.cs" />
8484
<Compile Include="Authentication\ServiceUrlBuilder.cs" />
85+
<Compile Include="Extensions\TypeExtensions.cs" />
8586
<Compile Include="Flurl\PreparedRequest.cs" />
8687
<Compile Include="Identifier.cs" />
8788
<Compile Include="Networking\IPVersion.cs" />
@@ -293,7 +294,6 @@
293294
<Compile Include="Core\Exceptions\Response\UserNotAuthorizedException.cs" />
294295
<Compile Include="Core\Providers\INetworksProvider.cs" />
295296
<Compile Include="Core\Validators\INetworksValidator.cs" />
296-
<Compile Include="Core\UserAgentGenerator.cs" />
297297
<Compile Include="Core\Validators\IHttpResponseCodeValidator.cs" />
298298
<Compile Include="Core\WebRequestExtensions.cs" />
299299
<Compile Include="Providers\Rackspace\CloudBlockStorageProvider.cs" />

src/corelib/OpenStackNet.cs

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
4+
using System.Extensions;
5+
using System.Net.Http.Headers;
36
using Flurl.Http;
47
using Flurl.Http.Configuration;
8+
using net.openstack.Core;
59
using Newtonsoft.Json;
610
using OpenStack.Authentication;
711
using OpenStack.Serialization;
@@ -14,26 +18,28 @@ namespace OpenStack
1418
/// <threadsafety static="true" instance="false"/>
1519
public static class OpenStackNet
1620
{
21+
/// <summary>
22+
/// Global configuration which affects OpenStack.NET's behavior.
23+
/// <para>Modify using <see cref="Configure"/>.</para>
24+
/// </summary>
25+
public static readonly OpenStackNetConfigurationOptions Configuration = new OpenStackNetConfigurationOptions();
1726
private static readonly object ConfigureLock = new object();
18-
private static bool _isConfigured;
1927

2028
/// <summary>
2129
/// Provides thread-safe accesss to OpenStack.NET's global configuration options.
2230
/// <para>
2331
/// Can only be called once at application start-up, before instantiating any OpenStack.NET objects.
2432
/// </para>
2533
/// </summary>
26-
/// <param name="configureFlurl">Addtional configuration of Flurl's global settings <seealso cref="Flurl.Http.FlurlHttp.Configure"/>.</param>
27-
/// <param name="configureJson">Additional configuration of Json.NET's glboal settings <seealso cref="Newtonsoft.Json.JsonConvert.DefaultSettings"/>.</param>
28-
public static void Configure(Action<FlurlHttpConfigurationOptions> configureFlurl = null, Action<JsonSerializerSettings> configureJson = null)
34+
/// <param name="configureFlurl">Addtional configuration of Flurl's global settings <seealso cref="Flurl.Http.FlurlHttp.Configure" />.</param>
35+
/// <param name="configureJson">Additional configuration of Json.NET's global settings <seealso cref="Newtonsoft.Json.JsonConvert.DefaultSettings" />.</param>
36+
/// <param name="configure">Additional configuration of OpenStack.NET's global settings.</param>
37+
public static void Configure(Action<FlurlHttpConfigurationOptions> configureFlurl = null, Action<JsonSerializerSettings> configureJson = null, Action<OpenStackNetConfigurationOptions> configure = null)
2938
{
30-
if (_isConfigured)
31-
return;
32-
3339
lock (ConfigureLock)
3440
{
35-
if (_isConfigured)
36-
return;
41+
if(configure != null)
42+
configure(Configuration);
3743

3844
JsonConvert.DefaultSettings = () =>
3945
{
@@ -51,20 +57,48 @@ public static void Configure(Action<FlurlHttpConfigurationOptions> configureFlur
5157
configureJson(settings);
5258
return settings;
5359
};
54-
60+
5561
FlurlHttp.Configure(c =>
5662
{
63+
// Apply the application's default settings
64+
if (configureFlurl != null)
65+
configureFlurl(c);
66+
5767
// Apply our default settings
5868
c.HttpClientFactory = new AuthenticatedHttpClientFactory();
59-
c.AfterCall = Tracing.TraceHttpCall;
60-
c.OnError = Tracing.TraceFailedHttpCall;
6169

62-
// Apply application's default settings
63-
if (configureFlurl != null)
64-
configureFlurl(c);
70+
var applicationBeforeCall = c.BeforeCall;
71+
c.BeforeCall = call =>
72+
{
73+
SetUserAgentHeader(call);
74+
if (applicationBeforeCall != null)
75+
applicationBeforeCall(call);
76+
};
77+
78+
var applicationAfterCall = c.AfterCall;
79+
c.AfterCall = call =>
80+
{
81+
Tracing.TraceHttpCall(call);
82+
if (applicationAfterCall != null)
83+
applicationAfterCall(call);
84+
};
85+
86+
var applicationOnError = c.OnError;
87+
c.OnError = call =>
88+
{
89+
Tracing.TraceFailedHttpCall(call);
90+
if (applicationOnError != null)
91+
applicationOnError(call);
92+
};
6593
});
94+
}
95+
}
6696

67-
_isConfigured = true;
97+
private static void SetUserAgentHeader(HttpCall call)
98+
{
99+
foreach (var userAgent in Configuration.UserAgents)
100+
{
101+
call.Request.Headers.UserAgent.Add(userAgent);
68102
}
69103
}
70104

@@ -101,4 +135,33 @@ public static void TraceHttpCall(HttpCall httpCall)
101135
}
102136
}
103137
}
138+
139+
/// <summary>
140+
/// A set of properties that affect OpenStack.NET's behavior.
141+
/// <para>Generally set via the static <see cref="OpenStack.OpenStackNet.Configure"/> method.</para>
142+
/// </summary>
143+
public class OpenStackNetConfigurationOptions
144+
{
145+
/// <summary/>
146+
protected internal OpenStackNetConfigurationOptions()
147+
{
148+
ResetDefaults();
149+
}
150+
151+
/// <summary>
152+
/// Additional application specific user agents which should be set in the UserAgent header on all requests.
153+
/// </summary>
154+
public List<ProductInfoHeaderValue> UserAgents { get; private set; }
155+
156+
/// <summary>
157+
/// Clear all custom global options and set default values.
158+
/// </summary>
159+
public virtual void ResetDefaults()
160+
{
161+
UserAgents = new List<ProductInfoHeaderValue>
162+
{
163+
new ProductInfoHeaderValue("openstack.net", GetType().GetAssemblyFileVersion())
164+
};
165+
}
166+
}
104167
}

src/corelib/Providers/Rackspace/ProviderBase`1.cs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.IO;
44
using System.Linq;
55
using System.Net;
6+
using System.Net.Http.Headers;
67
using System.Net.Mime;
78
using System.Threading.Tasks;
89
using JSIStudios.SimpleRESTServices.Client;
@@ -220,26 +221,17 @@ protected virtual IBackoffPolicy DefaultBackoffPolicy
220221
/// <summary>
221222
/// Gets the default value for the <strong>User-Agent</strong> header for HTTP requests sent by this provider.
222223
/// </summary>
223-
/// <remarks>
224-
/// <para>If the <see cref="ApplicationUserAgent"/> property is not set, this property simply returns
225-
/// <see cref="UserAgentGenerator.UserAgent"/>.</para>
226-
/// <para>If the <see cref="ApplicationUserAgent"/> property is set, this property returns a two-part user agent
227-
/// starting with the <see cref="ApplicationUserAgent"/> value, followed by the
228-
/// <see cref="UserAgentGenerator.UserAgent"/> value.</para>
229-
/// </remarks>
230224
/// <value>
231225
/// The default value for the <strong>User-Agent</strong> header for HTTP requests sent by this provider.
232226
/// </value>
233227
protected string DefaultUserAgent
234228
{
235229
get
236230
{
237-
string userAgent = UserAgentGenerator.UserAgent;
238-
string applicationUserAgent = ApplicationUserAgent;
239-
if (!string.IsNullOrEmpty(applicationUserAgent))
240-
return applicationUserAgent + " " + userAgent;
241-
242-
return userAgent;
231+
List<string> userAgents = OpenStack.OpenStackNet.Configuration.UserAgents.Select(x => x.ToString()).ToList();
232+
if(!string.IsNullOrEmpty(ApplicationUserAgent))
233+
userAgents.Add(ApplicationUserAgent);
234+
return string.Join(" ", userAgents);
243235
}
244236
}
245237

src/testing/integration/TestIdentityProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Net.Http.Headers;
23
using net.openstack.Core.Domain;
34
using net.openstack.Core.Providers;
45

@@ -19,7 +20,7 @@ public static IIdentityProvider GetIdentityProvider()
1920
var identityEndpoint = GetIdentityEndpointFromEnvironment();
2021
return new OpenStackIdentityProvider(identityEndpoint, identity)
2122
{
22-
ApplicationUserAgent = "CI-BOT"
23+
ApplicationUserAgent = new ProductInfoHeaderValue("(CI-BOT)").ToString()
2324
};
2425
}
2526

src/testing/unit/OpenStack.UnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
<Compile Include="Networking\v2\PortTests.cs" />
109109
<Compile Include="Networking\v2\SubnetTests.cs" />
110110
<Compile Include="Networking\v2\NetworkTests.cs" />
111+
<Compile Include="OpenStackNetTests.cs" />
111112
<Compile Include="Properties\AssemblyInfo.cs" />
112113
<Compile Include="Providers\Rackspace\CloudNetworksValidatorTests.cs" />
113114
<Compile Include="Providers\Rackspace\CloudBlockStorageTests.cs" />
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System;
2+
using System.Net.Http.Headers;
3+
using Flurl.Http;
4+
using OpenStack.Testing;
5+
using Xunit;
6+
7+
namespace OpenStack
8+
{
9+
public class OpenStackNetTests : IDisposable
10+
{
11+
public void Dispose()
12+
{
13+
OpenStackNet.Configuration.ResetDefaults();
14+
}
15+
16+
[Fact]
17+
public async void UserAgentTest()
18+
{
19+
using (var httpTest = new HttpTest())
20+
{
21+
OpenStackNet.Configure();
22+
23+
await "http://api.com".GetAsync();
24+
25+
var userAgent = httpTest.CallLog[0].Request.Headers.UserAgent.ToString();
26+
Assert.Contains("openstack.net", userAgent);
27+
}
28+
}
29+
30+
[Fact]
31+
public async void UserAgentWithApplicationSuffixTest()
32+
{
33+
using (var httpTest = new HttpTest())
34+
{
35+
OpenStackNet.Configure(configure: options => options.UserAgents.Add(new ProductInfoHeaderValue("(unittests)")));
36+
37+
await "http://api.com".GetAsync();
38+
39+
var userAgent = httpTest.CallLog[0].Request.Headers.UserAgent.ToString();
40+
Assert.Contains("openstack.net", userAgent);
41+
Assert.Contains("unittests", userAgent);
42+
}
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)