Skip to content

Commit 9f7d593

Browse files
rose-aKirovAirsungam3r
authored
Feature/make automatic header optional (#542)
* Make UserAgent request header optional. * Changed to configurable user agent header. * Update src/GraphQL.Client/GraphQLHttpClientOptions.cs Co-authored-by: Alexander Rose <alex@rose-a.de> * Update src/GraphQL.Client/GraphQLHttpRequest.cs Co-authored-by: Alexander Rose <alex@rose-a.de> * Update src/GraphQL.Client/GraphQLHttpRequest.cs Co-authored-by: Alexander Rose <alex@rose-a.de> * Update src/GraphQL.Client/GraphQLHttpRequest.cs Co-authored-by: Alexander Rose <alex@rose-a.de> * Update src/GraphQL.Client/GraphQLHttpRequest.cs * refactor test helpers to provide access to GraphQLHttpClientOptions when creating the test client * test user agent header --------- Co-authored-by: Jesse <info@jessesander.nl> Co-authored-by: Ivan Maximov <sungam3r@yandex.ru>
1 parent 4430c20 commit 9f7d593

File tree

11 files changed

+145
-61
lines changed

11 files changed

+145
-61
lines changed

src/GraphQL.Client/GraphQLHttpClient.cs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,13 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson
6060
_disposeHttpClient = true;
6161
}
6262

63-
public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient)
64-
{
65-
Options = options ?? throw new ArgumentNullException(nameof(options));
66-
JsonSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use");
67-
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
68-
69-
if (!HttpClient.DefaultRequestHeaders.UserAgent.Any())
70-
HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GetType().Assembly.GetName().Name, GetType().Assembly.GetName().Version.ToString()));
71-
72-
_lazyHttpWebSocket = new Lazy<GraphQLHttpWebSocket>(CreateGraphQLHttpWebSocket);
73-
}
63+
public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient)
64+
{
65+
Options = options ?? throw new ArgumentNullException(nameof(options));
66+
JsonSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use");
67+
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
68+
_lazyHttpWebSocket = new Lazy<GraphQLHttpWebSocket>(CreateGraphQLHttpWebSocket);
69+
}
7470

7571
#endregion
7672

src/GraphQL.Client/GraphQLHttpClientOptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,11 @@ public class GraphQLHttpClientOptions
8080
/// See https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init.
8181
/// </summary>
8282
public Func<GraphQLHttpClientOptions, object?> ConfigureWebSocketConnectionInitPayload { get; set; } = options => null;
83+
84+
/// <summary>
85+
/// The default user agent request header.
86+
/// Default to the GraphQL client assembly.
87+
/// </summary>
88+
public ProductInfoHeaderValue? DefaultUserAgentRequestHeader { get; set; }
89+
= new ProductInfoHeaderValue(typeof(GraphQLHttpClient).Assembly.GetName().Name, typeof(GraphQLHttpClient).Assembly.GetName().Version.ToString());
8390
}

src/GraphQL.Client/GraphQLHttpRequest.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions
4343
message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
4444
message.Headers.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8"));
4545

46+
if (options.DefaultUserAgentRequestHeader != null)
47+
message.Headers.UserAgent.Add(options.DefaultUserAgentRequestHeader);
48+
4649
#pragma warning disable CS0618 // Type or member is obsolete
4750
PreprocessHttpRequestMessage(message);
4851
#pragma warning restore CS0618 // Type or member is obsolete

tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
using GraphQL.Types;
2+
using Microsoft.AspNetCore.Http;
3+
using Microsoft.Extensions.DependencyInjection;
24

35
namespace GraphQL.Client.Tests.Common.Chat.Schema;
46

57
public class ChatQuery : ObjectGraphType
68
{
9+
private readonly IServiceProvider _serviceProvider;
10+
711
public static readonly Dictionary<string, object> TestExtensions = new()
812
{
913
{"extension1", "hello world"},
@@ -16,8 +20,9 @@ public class ChatQuery : ObjectGraphType
1620
public readonly ManualResetEventSlim LongRunningQueryBlocker = new ManualResetEventSlim();
1721
public readonly ManualResetEventSlim WaitingOnQueryBlocker = new ManualResetEventSlim();
1822

19-
public ChatQuery(IChat chat)
23+
public ChatQuery(IChat chat, IServiceProvider serviceProvider)
2024
{
25+
_serviceProvider = serviceProvider;
2126
Name = "ChatQuery";
2227

2328
Field<ListGraphType<MessageType>>("messages").Resolve(context => chat.AllMessages.Take(100));
@@ -37,5 +42,17 @@ public ChatQuery(IChat chat)
3742
WaitingOnQueryBlocker.Reset();
3843
return "finally returned";
3944
});
45+
46+
Field<StringGraphType>("clientUserAgent")
47+
.Resolve(context =>
48+
{
49+
var contextAccessor = _serviceProvider.GetRequiredService<IHttpContextAccessor>();
50+
if (!contextAccessor.HttpContext.Request.Headers.UserAgent.Any())
51+
{
52+
context.Errors.Add(new ExecutionError("user agent header not set"));
53+
return null;
54+
}
55+
return contextAccessor.HttpContext.Request.Headers.UserAgent.ToString();
56+
});
4057
}
4158
}

tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netstandard2.0</TargetFramework>
4+
<TargetFramework>net7.0</TargetFramework>
55
<IsPackable>false</IsPackable>
66
</PropertyGroup>
77

@@ -16,4 +16,7 @@
1616
<ProjectReference Include="..\..\src\GraphQL.Client\GraphQL.Client.csproj" />
1717
</ItemGroup>
1818

19+
<ItemGroup>
20+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
21+
</ItemGroup>
1922
</Project>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"profiles": {
3+
"GraphQL.Client.Tests.Common": {
4+
"commandName": "Project",
5+
"launchBrowser": true,
6+
"environmentVariables": {
7+
"ASPNETCORE_ENVIRONMENT": "Development"
8+
},
9+
"applicationUrl": "http://localhost:59034"
10+
}
11+
}
12+
}

tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public abstract class IntegrationServerTestFixture
1212
{
1313
public int Port { get; private set; }
1414

15-
public IWebHost Server { get; private set; }
15+
public IWebHost? Server { get; private set; }
1616

1717
public abstract IGraphQLWebsocketJsonSerializer Serializer { get; }
1818

@@ -27,7 +27,7 @@ public async Task CreateServer()
2727
{
2828
if (Server != null)
2929
return;
30-
Server = await WebHostHelpers.CreateServer(Port);
30+
Server = await WebHostHelpers.CreateServer(Port).ConfigureAwait(false);
3131
}
3232

3333
public async Task ShutdownServer()
@@ -40,18 +40,20 @@ public async Task ShutdownServer()
4040
Server = null;
4141
}
4242

43-
public GraphQLHttpClient GetStarWarsClient(bool requestsViaWebsocket = false)
44-
=> GetGraphQLClient(Common.STAR_WARS_ENDPOINT, requestsViaWebsocket);
43+
public GraphQLHttpClient GetStarWarsClient(Action<GraphQLHttpClientOptions>? configure = null)
44+
=> GetGraphQLClient(Common.STAR_WARS_ENDPOINT, configure);
4545

46-
public GraphQLHttpClient GetChatClient(bool requestsViaWebsocket = false)
47-
=> GetGraphQLClient(Common.CHAT_ENDPOINT, requestsViaWebsocket);
46+
public GraphQLHttpClient GetChatClient(Action<GraphQLHttpClientOptions>? configure = null)
47+
=> GetGraphQLClient(Common.CHAT_ENDPOINT, configure);
4848

49-
private GraphQLHttpClient GetGraphQLClient(string endpoint, bool requestsViaWebsocket = false)
50-
{
51-
if (Serializer == null)
52-
throw new InvalidOperationException("JSON serializer not configured");
53-
return WebHostHelpers.GetGraphQLClient(Port, endpoint, requestsViaWebsocket, Serializer, WebsocketProtocol);
54-
}
49+
private GraphQLHttpClient GetGraphQLClient(string endpoint, Action<GraphQLHttpClientOptions>? configure) =>
50+
Serializer == null
51+
? throw new InvalidOperationException("JSON serializer not configured")
52+
: WebHostHelpers.GetGraphQLClient(Port, endpoint, Serializer, options =>
53+
{
54+
configure?.Invoke(options);
55+
options.WebSocketProtocol = WebsocketProtocol;
56+
});
5557
}
5658

5759
public class NewtonsoftGraphQLWsServerTestFixture : IntegrationServerTestFixture

tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs

Lines changed: 9 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
using GraphQL.Client.Http;
33
using GraphQL.Client.Http.Websocket;
44
using GraphQL.Client.Serializer.Newtonsoft;
5-
using GraphQL.Client.Tests.Common;
6-
using GraphQL.Client.Tests.Common.Helpers;
75
using IntegrationTestServer;
86

97
namespace GraphQL.Integration.Tests.Helpers;
@@ -31,37 +29,15 @@ public static async Task<IWebHost> CreateServer(int port)
3129
return host;
3230
}
3331

34-
public static GraphQLHttpClient GetGraphQLClient(int port, string endpoint, bool requestsViaWebsocket = false, IGraphQLWebsocketJsonSerializer serializer = null, string websocketProtocol = WebSocketProtocols.GRAPHQL_WS)
35-
=> new GraphQLHttpClient(new GraphQLHttpClientOptions
36-
{
37-
EndPoint = new Uri($"http://localhost:{port}{endpoint}"),
38-
UseWebSocketForQueriesAndMutations = requestsViaWebsocket,
39-
WebSocketProtocol = websocketProtocol
40-
},
41-
serializer ?? new NewtonsoftJsonSerializer());
42-
}
43-
44-
public class TestServerSetup : IDisposable
45-
{
46-
public TestServerSetup(IGraphQLWebsocketJsonSerializer serializer)
32+
public static GraphQLHttpClient GetGraphQLClient(
33+
int port,
34+
string endpoint,
35+
IGraphQLWebsocketJsonSerializer? serializer = null,
36+
Action<GraphQLHttpClientOptions>? configure = null)
4737
{
48-
Serializer = serializer;
49-
Port = NetworkHelpers.GetFreeTcpPortNumber();
38+
var options = new GraphQLHttpClientOptions();
39+
configure?.Invoke(options);
40+
options.EndPoint = new Uri($"http://localhost:{port}{endpoint}");
41+
return new GraphQLHttpClient(options, serializer ?? new NewtonsoftJsonSerializer());
5042
}
51-
52-
public int Port { get; }
53-
54-
public IWebHost Server { get; set; }
55-
56-
public IGraphQLWebsocketJsonSerializer Serializer { get; set; }
57-
58-
public GraphQLHttpClient GetStarWarsClient(bool requestsViaWebsocket = false)
59-
=> GetGraphQLClient(Common.STAR_WARS_ENDPOINT, requestsViaWebsocket);
60-
61-
public GraphQLHttpClient GetChatClient(bool requestsViaWebsocket = false)
62-
=> GetGraphQLClient(Common.CHAT_ENDPOINT, requestsViaWebsocket);
63-
64-
private GraphQLHttpClient GetGraphQLClient(string endpoint, bool requestsViaWebsocket = false) => WebHostHelpers.GetGraphQLClient(Port, endpoint, requestsViaWebsocket);
65-
66-
public void Dispose() => Server?.Dispose();
6743
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System.Net.Http.Headers;
2+
using FluentAssertions;
3+
using GraphQL.Client.Abstractions;
4+
using GraphQL.Client.Http;
5+
using GraphQL.Integration.Tests.Helpers;
6+
using Xunit;
7+
8+
namespace GraphQL.Integration.Tests;
9+
10+
public class UserAgentHeaderTests : IAsyncLifetime, IClassFixture<SystemTextJsonAutoNegotiateServerTestFixture>
11+
{
12+
private readonly IntegrationServerTestFixture Fixture;
13+
private GraphQLHttpClient? ChatClient;
14+
15+
public UserAgentHeaderTests(SystemTextJsonAutoNegotiateServerTestFixture fixture)
16+
{
17+
Fixture = fixture;
18+
}
19+
20+
public async Task InitializeAsync() => await Fixture.CreateServer().ConfigureAwait(false);
21+
22+
public Task DisposeAsync()
23+
{
24+
ChatClient?.Dispose();
25+
return Task.CompletedTask;
26+
}
27+
28+
[Fact]
29+
public async void Can_set_custom_user_agent()
30+
{
31+
const string userAgent = "CustomUserAgent";
32+
ChatClient = Fixture.GetChatClient(options => options.DefaultUserAgentRequestHeader = ProductInfoHeaderValue.Parse(userAgent));
33+
34+
var graphQLRequest = new GraphQLRequest("query clientUserAgent { clientUserAgent }");
35+
var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }).ConfigureAwait(false);
36+
37+
response.Errors.Should().BeNull();
38+
response.Data.clientUserAgent.Should().Be(userAgent);
39+
}
40+
41+
[Fact]
42+
public async void Default_user_agent_is_set_as_expected()
43+
{
44+
string? expectedUserAgent = new ProductInfoHeaderValue(
45+
typeof(GraphQLHttpClient).Assembly.GetName().Name,
46+
typeof(GraphQLHttpClient).Assembly.GetName().Version.ToString()).ToString();
47+
48+
ChatClient = Fixture.GetChatClient();
49+
50+
var graphQLRequest = new GraphQLRequest("query clientUserAgent { clientUserAgent }");
51+
var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }).ConfigureAwait(false);
52+
53+
response.Errors.Should().BeNull();
54+
response.Data.clientUserAgent.Should().Be(expectedUserAgent);
55+
}
56+
57+
[Fact]
58+
public async void No_Default_user_agent_if_set_to_null()
59+
{
60+
ChatClient = Fixture.GetChatClient(options => options.DefaultUserAgentRequestHeader = null);
61+
62+
var graphQLRequest = new GraphQLRequest("query clientUserAgent { clientUserAgent }");
63+
var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }).ConfigureAwait(false);
64+
65+
response.Errors.Should().HaveCount(1);
66+
response.Errors[0].Message.Should().Be("user agent header not set");
67+
}
68+
}

tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public abstract class Base : IAsyncLifetime
2121
{
2222
protected readonly ITestOutputHelper Output;
2323
protected readonly IntegrationServerTestFixture Fixture;
24-
protected GraphQLHttpClient ChatClient;
24+
protected GraphQLHttpClient? ChatClient;
2525

2626
protected Base(ITestOutputHelper output, IntegrationServerTestFixture fixture)
2727
{
@@ -43,7 +43,7 @@ public async Task InitializeAsync()
4343
Fixture.Server.Services.GetService<Chat>().AddMessage(InitialMessage);
4444

4545
// then create the chat client
46-
ChatClient ??= Fixture.GetChatClient(true);
46+
ChatClient ??= Fixture.GetChatClient(options => options.UseWebSocketForQueriesAndMutations = true);
4747
}
4848

4949
public Task DisposeAsync()

0 commit comments

Comments
 (0)