Use service provider HttpMessageHandler factory for GitHub client

This commit is contained in:
Jordan Dominion
2025-02-16 17:35:33 -05:00
parent 65e4e9abc2
commit 3df4771f89
5 changed files with 70 additions and 13 deletions
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
@@ -12,6 +13,7 @@ using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Octokit;
using Octokit.Internal;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
@@ -38,6 +40,11 @@ namespace Tgstation.Server.Host.Utils.GitHub
/// </summary>
readonly IAssemblyInformationProvider assemblyInformationProvider;
/// <summary>
/// The <see cref="IHttpMessageHandlerFactory"/> for the <see cref="GitHubClientFactory"/>.
/// </summary>
readonly IHttpMessageHandlerFactory httpMessageHandlerFactory;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="GitHubClientFactory"/>.
/// </summary>
@@ -62,14 +69,17 @@ namespace Tgstation.Server.Host.Utils.GitHub
/// Initializes a new instance of the <see cref="GitHubClientFactory"/> class.
/// </summary>
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
/// <param name="httpMessageHandlerFactory">The value of <see cref="httpMessageHandlerFactory"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
public GitHubClientFactory(
IAssemblyInformationProvider assemblyInformationProvider,
IHttpMessageHandlerFactory httpMessageHandlerFactory,
ILogger<GitHubClientFactory> logger,
IOptions<GeneralConfiguration> generalConfigurationOptions)
{
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
this.httpMessageHandlerFactory = httpMessageHandlerFactory ?? throw new ArgumentNullException(nameof(httpMessageHandlerFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
@@ -315,10 +325,33 @@ namespace Tgstation.Server.Host.Utils.GitHub
GitHubClient CreateUnauthenticatedClient()
{
var product = assemblyInformationProvider.ProductInfoHeaderValue.Product!;
return new GitHubClient(
new ProductHeaderValue(
product.Name,
product.Version));
#pragma warning disable CA2000 // Dispose objects before losing scope
var handler = httpMessageHandlerFactory.CreateHandler();
try
{
var clientAdapter = new HttpClientAdapter(() => handler);
#pragma warning restore CA2000 // Dispose objects before losing scope
handler = null;
try
{
return new GitHubClient(
new Connection(
new ProductHeaderValue(
product.Name,
product.Version),
clientAdapter));
}
catch
{
clientAdapter.Dispose();
throw;
}
}
catch
{
handler?.Dispose();
throw;
}
}
}
}
@@ -0,0 +1,19 @@
using System;
using System.Net.Http;
namespace Tgstation.Server.Host.Tests
{
/// <summary>
/// Basic <see cref="IHttpMessageHandlerFactory"/> implementation for testiong
/// </summary>
public sealed class BasicHttpMessageHandlerFactory : IHttpMessageHandlerFactory, IDisposable
{
readonly HttpClientHandler handler = new();
public HttpMessageHandler CreateHandler(string name)
=> handler;
public void Dispose()
=> handler.Dispose();
}
}
@@ -1,4 +1,5 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
@@ -15,6 +16,7 @@ using Octokit;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
using Tgstation.Server.Host.Tests;
namespace Tgstation.Server.Host.Utils.GitHub.Tests
{
@@ -42,9 +44,10 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
[TestMethod]
public void TestContructionThrows()
{
Assert.ThrowsException<ArgumentNullException>(() => new GitHubClientFactory(null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new GitHubClientFactory(Mock.Of<IAssemblyInformationProvider>(), null, null));
Assert.ThrowsException<ArgumentNullException>(() => new GitHubClientFactory(Mock.Of<IAssemblyInformationProvider>(), Mock.Of<ILogger<GitHubClientFactory>>(), null));
Assert.ThrowsException<ArgumentNullException>(() => new GitHubClientFactory(null, null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new GitHubClientFactory(Mock.Of<IAssemblyInformationProvider>(), null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new GitHubClientFactory(Mock.Of<IAssemblyInformationProvider>(), Mock.Of<IHttpMessageHandlerFactory>(), null, null));
Assert.ThrowsException<ArgumentNullException>(() => new GitHubClientFactory(Mock.Of<IAssemblyInformationProvider>(), Mock.Of<IHttpMessageHandlerFactory>(), Mock.Of<ILogger<GitHubClientFactory>>(), null));
}
[TestMethod]
@@ -58,7 +61,7 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
var gc = new GeneralConfiguration();
Assert.IsNull(gc.GitHubAccessToken);
mockOptions.SetupGet(x => x.Value).Returns(gc);
var factory = new GitHubClientFactory(mockApp.Object, loggerFactory.CreateLogger<GitHubClientFactory>(), mockOptions.Object);
var factory = new GitHubClientFactory(mockApp.Object, new BasicHttpMessageHandlerFactory(), loggerFactory.CreateLogger<GitHubClientFactory>(), mockOptions.Object);
var client = await factory.CreateClient(CancellationToken.None);
Assert.IsNotNull(client);
@@ -84,7 +87,7 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
var mockOptions = new Mock<IOptions<GeneralConfiguration>>();
mockOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
var factory = new GitHubClientFactory(mockApp.Object, loggerFactory.CreateLogger<GitHubClientFactory>(), mockOptions.Object);
var factory = new GitHubClientFactory(mockApp.Object, new BasicHttpMessageHandlerFactory(), loggerFactory.CreateLogger<GitHubClientFactory>(), mockOptions.Object);
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => factory.CreateClient(null, CancellationToken.None).AsTask());
@@ -106,7 +109,7 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
var mockOptions = new Mock<IOptions<GeneralConfiguration>>();
mockOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
var factory = new GitHubClientFactory(mockApp.Object, loggerFactory.CreateLogger<GitHubClientFactory>(), mockOptions.Object);
var factory = new GitHubClientFactory(mockApp.Object, new BasicHttpMessageHandlerFactory(), loggerFactory.CreateLogger<GitHubClientFactory>(), mockOptions.Object);
var appID = Environment.GetEnvironmentVariable("TGS_TEST_APP_ID");
var privateKey = Environment.GetEnvironmentVariable("TGS_TEST_APP_PRIVATE_KEY");
@@ -144,7 +147,7 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
var mockOptions = new Mock<IOptions<GeneralConfiguration>>();
mockOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
var factory = new GitHubClientFactory(mockApp.Object, loggerFactory.CreateLogger<GitHubClientFactory>(), mockOptions.Object);
var factory = new GitHubClientFactory(mockApp.Object, new BasicHttpMessageHandlerFactory(), loggerFactory.CreateLogger<GitHubClientFactory>(), mockOptions.Object);
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => factory.CreateClient(null, CancellationToken.None).AsTask());
@@ -192,7 +195,7 @@ vTdVAoGBAI/jjUMdjkY43zhe3w2piwT0fhGfqm9ikdAB9IcgcptuS0ML0ZaWV/eO
var mockOptions = new Mock<IOptions<GeneralConfiguration>>();
mockOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
var factory = new GitHubClientFactory(mockApp.Object, loggerFactory.CreateLogger<GitHubClientFactory>(), mockOptions.Object);
var factory = new GitHubClientFactory(mockApp.Object, new BasicHttpMessageHandlerFactory(), loggerFactory.CreateLogger<GitHubClientFactory>(), mockOptions.Object);
var client1 = await factory.CreateClient(CancellationToken.None);
var client2 = await factory.CreateClient("asdf", CancellationToken.None);
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@@ -38,7 +39,7 @@ namespace Tgstation.Server.Tests.Live
GitHubAccessToken = Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN")
});
var gitHubClientFactory = new GitHubClientFactory(new AssemblyInformationProvider(), Mock.Of<ILogger<GitHubClientFactory>>(), mockOptions.Object);
var gitHubClientFactory = new GitHubClientFactory(new AssemblyInformationProvider(), Mock.Of<IHttpMessageHandlerFactory>(), Mock.Of<ILogger<GitHubClientFactory>>(), mockOptions.Object);
RealClient = gitHubClientFactory.CreateClient(CancellationToken.None).GetAwaiter().GetResult();
}
@@ -13,6 +13,7 @@
<ProjectReference Include="..\..\src\Tgstation.Server.Client.GraphQL\Tgstation.Server.Client.GraphQL.csproj" />
<ProjectReference Include="..\..\src\Tgstation.Server.Host.Watchdog\Tgstation.Server.Host.Watchdog.csproj" />
<ProjectReference Include="..\..\src\Tgstation.Server.Host\Tgstation.Server.Host.csproj" />
<ProjectReference Include="..\Tgstation.Server.Host.Tests\Tgstation.Server.Host.Tests.csproj" />
</ItemGroup>
<ItemGroup>