Skip to content

Commit fa01406

Browse files
authored
Use a shared client for remote app connections (#178)
This change brings the configuration of the HttpClient into a shared location for all the remote app connections. Now, any system that needs to connect to the remote app can do so by just retrieving the client via the IHttpClientFactory by name and the path/URI/backchannel will be configured for them.
1 parent e05622c commit fa01406

File tree

9 files changed

+42
-53
lines changed

9 files changed

+42
-53
lines changed

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/Authentication/RemoteAppAuthenticationExtensions.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,6 @@ public static AuthenticationBuilder AddRemoteAppAuthentication(this Authenticati
5757

5858
authenticationBuilder.Services.AddScoped<IRemoteAppAuthenticationResultProcessor, RedirectUrlProcessor>();
5959
authenticationBuilder.Services.AddSingleton<IAuthenticationResultFactory, RemoteAppAuthenticationResultFactory>();
60-
authenticationBuilder.Services.AddHttpClient(AuthenticationConstants.AuthClientName)
61-
// Disable cookies in the HTTP client because the service will manage the cookie header directly
62-
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { UseCookies = false, AllowAutoRedirect = false });
6360
authenticationBuilder.Services.AddTransient<IRemoteAppAuthenticationService, RemoteAppAuthenticationService>();
6461
authenticationBuilder.Services.AddOptions<RemoteAppAuthenticationClientOptions>(scheme)
6562
.Configure(configureOptions ?? (_ => { }))

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/Authentication/RemoteAppAuthenticationService.cs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ internal partial class RemoteAppAuthenticationService : IRemoteAppAuthentication
2525
private readonly IAuthenticationResultFactory _resultFactory;
2626
private readonly ILogger<RemoteAppAuthenticationService> _logger;
2727
private readonly IOptionsSnapshot<RemoteAppAuthenticationClientOptions> _authOptionsSnapshot;
28-
private readonly RemoteAppClientOptions _remoteAppOptions;
2928

3029
private RemoteAppAuthenticationClientOptions? _options;
3130

@@ -36,14 +35,15 @@ public RemoteAppAuthenticationService(
3635
IOptions<RemoteAppClientOptions> remoteAppOptions,
3736
ILogger<RemoteAppAuthenticationService> logger)
3837
{
39-
_remoteAppOptions = remoteAppOptions?.Value ?? throw new ArgumentNullException(nameof(remoteAppOptions));
38+
if (httpClientFactory is null)
39+
{
40+
throw new ArgumentNullException(nameof(httpClientFactory));
41+
}
42+
4043
_resultFactory = resultFactory ?? throw new ArgumentNullException(nameof(resultFactory));
4144
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
4245
_authOptionsSnapshot = authOptions ?? throw new ArgumentNullException(nameof(authOptions));
43-
44-
// Use the HttpClient supplied in options if one is present;
45-
// otherwise, generate a client with an IHttpClientFactory from DI
46-
_client = _remoteAppOptions.BackchannelHttpClient ?? httpClientFactory?.CreateClient(AuthenticationConstants.AuthClientName) ?? throw new ArgumentNullException(nameof(httpClientFactory));
46+
_client = httpClientFactory.CreateClient(RemoteConstants.HttpClientName);
4747
}
4848

4949
/// <summary>
@@ -55,8 +55,6 @@ public Task InitializeAsync(AuthenticationScheme scheme)
5555
// Finish initializing the http client here since the scheme won't be known
5656
// until the owning authentication handler is initialized.
5757
_options = _authOptionsSnapshot.Get(scheme.Name);
58-
_client.BaseAddress = new Uri($"{_remoteAppOptions.RemoteAppUrl.ToString().TrimEnd('/')}{_options.AuthenticationEndpointPath}");
59-
_client.DefaultRequestHeaders.Add(_remoteAppOptions.ApiKeyHeader, _remoteAppOptions.ApiKey);
6058

6159
return Task.CompletedTask;
6260
}
@@ -87,7 +85,7 @@ public async Task<RemoteAppAuthenticationResult> AuthenticateAsync(HttpRequest o
8785
// that may matter for authentication. Also include the original request path as
8886
// as a query parameter so that the ASP.NET app can redirect back to it if an
8987
// authentication provider attempts to redirect back to the authenticate URL.
90-
var url = $"?{AuthenticationConstants.OriginalUrlQueryParamName}={WebUtility.UrlEncode(originalRequest.GetEncodedPathAndQuery())}";
88+
var url = $"{_options.AuthenticationEndpointPath}?{AuthenticationConstants.OriginalUrlQueryParamName}={WebUtility.UrlEncode(originalRequest.GetEncodedPathAndQuery())}";
9189
using var authRequest = new HttpRequestMessage(HttpMethod.Get, url);
9290
AddHeaders(_options.RequestHeadersToForward, originalRequest, authRequest);
9391

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/RemoteAppClientExtensions.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.Net.Http;
56
using Microsoft.AspNetCore.SystemWebAdapters;
7+
using Microsoft.Extensions.Options;
68

79
namespace Microsoft.Extensions.DependencyInjection;
810

@@ -27,6 +29,27 @@ public static ISystemWebAdapterBuilder AddRemoteAppClient(this ISystemWebAdapter
2729
builder.Services.AddOptions<RemoteAppClientOptions>()
2830
.ValidateDataAnnotations();
2931

32+
builder.Services.AddHttpClient(RemoteConstants.HttpClientName)
33+
.ConfigurePrimaryHttpMessageHandler(sp =>
34+
{
35+
var options = sp.GetRequiredService<IOptions<RemoteAppClientOptions>>().Value;
36+
37+
if (options.BackchannelHandler is { } handler)
38+
{
39+
return handler;
40+
}
41+
42+
// Disable cookies in the HTTP client because the service will manage the cookie header directly
43+
return new HttpClientHandler { UseCookies = false, AllowAutoRedirect = false };
44+
})
45+
.ConfigureHttpClient((sp, client) =>
46+
{
47+
var options = sp.GetRequiredService<IOptions<RemoteAppClientOptions>>().Value;
48+
49+
client.BaseAddress = options.RemoteAppUrl;
50+
client.DefaultRequestHeaders.Add(options.ApiKeyHeader, options.ApiKey);
51+
});
52+
3053
configure(new Builder(builder.Services));
3154

3255
return builder;

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/RemoteAppClientOptions.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ public class RemoteAppClientOptions
3131
public Uri RemoteAppUrl { get; set; } = null!;
3232

3333
/// <summary>
34-
/// Gets or sets an <see cref="HttpClient"/> to use for making requests to the remote app.
35-
/// A new <see cref="HttpClient"/> will be automatically generated if none is provided.
34+
/// Gets or sets an <see cref="HttpMessageHandler"/> to use for making requests to the remote app.
3635
/// </summary>
37-
public HttpClient? BackchannelHttpClient { get; set; }
36+
public HttpMessageHandler? BackchannelHandler { get; set; }
3837
}

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionState/RemoteSession/RemoteAppSessionStateExtensions.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5-
using System.Net.Http;
65
using Microsoft.AspNetCore.SystemWebAdapters;
7-
using Microsoft.AspNetCore.SystemWebAdapters.SessionState;
86
using Microsoft.AspNetCore.SystemWebAdapters.SessionState.RemoteSession;
97

108
namespace Microsoft.Extensions.DependencyInjection;
@@ -18,10 +16,6 @@ public static ISystemWebAdapterRemoteClientAppBuilder AddSession(this ISystemWeb
1816
throw new ArgumentNullException(nameof(builder));
1917
}
2018

21-
builder.Services.AddHttpClient(SessionConstants.SessionClientName)
22-
// Disable cookies in the HTTP client because the service will manage the cookie header directly
23-
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { UseCookies = false });
24-
2519
builder.Services.AddTransient<ISessionManager, RemoteAppSessionStateManager>();
2620

2721
builder.Services.AddOptions<RemoteAppSessionStateClientOptions>()

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionState/RemoteSession/RemoteAppSessionStateManager.cs

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,36 +21,14 @@ internal partial class RemoteAppSessionStateManager : ISessionManager
2121
private readonly ISessionSerializer _serializer;
2222
private readonly ILogger<RemoteAppSessionStateManager> _logger;
2323
private readonly RemoteAppSessionStateClientOptions _options;
24-
private readonly IOptions<RemoteAppClientOptions> _remoteAppOptions;
25-
26-
private HttpClient? _client;
27-
28-
private HttpClient Client
29-
{
30-
get
31-
{
32-
if (_client is null)
33-
{
34-
// Use the HttpClient supplied in options if one is present in options;
35-
// otherwise, generate a client with an IHttpClientFactory from DI
36-
_client = _remoteAppOptions.Value.BackchannelHttpClient ?? _httpClientFactory.CreateClient(SessionConstants.SessionClientName);
37-
_client.BaseAddress = new Uri($"{_remoteAppOptions.Value.RemoteAppUrl.ToString().TrimEnd('/')}{_options.SessionEndpointPath}");
38-
_client.DefaultRequestHeaders.Add(_remoteAppOptions.Value.ApiKeyHeader, _remoteAppOptions.Value.ApiKey);
39-
}
40-
41-
return _client;
42-
}
43-
}
4424

4525
public RemoteAppSessionStateManager(
4626
IHttpClientFactory httpClientFactory,
4727
ISessionSerializer serializer,
48-
IOptions<RemoteAppSessionStateClientOptions> sessionOptions,
49-
IOptions<RemoteAppClientOptions> remoteAppOptions,
28+
IOptions<RemoteAppSessionStateClientOptions> options,
5029
ILogger<RemoteAppSessionStateManager> logger)
5130
{
52-
_remoteAppOptions = remoteAppOptions ?? throw new ArgumentNullException(nameof(remoteAppOptions));
53-
_options = sessionOptions?.Value ?? throw new ArgumentNullException(nameof(sessionOptions));
31+
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
5432
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
5533
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
5634
_serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
@@ -95,13 +73,14 @@ private async Task<ISessionState> GetSessionDataAsync(string? sessionId, bool re
9573
{
9674
// The request message is manually disposed at a later time
9775
#pragma warning disable CA2000 // Dispose objects before losing scope
98-
var req = new HttpRequestMessage { Method = HttpMethod.Get };
76+
var req = new HttpRequestMessage(HttpMethod.Get, _options.SessionEndpointPath);
9977
#pragma warning restore CA2000 // Dispose objects before losing scope
10078

10179
AddSessionCookieToHeader(req, sessionId);
10280
AddReadOnlyHeader(req, readOnly);
10381

104-
var response = await Client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, token);
82+
using var client = _httpClientFactory.CreateClient(RemoteConstants.HttpClientName);
83+
var response = await client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, token);
10584

10685
LogRetrieveResponse(response.StatusCode);
10786

@@ -133,15 +112,16 @@ private async Task<ISessionState> GetSessionDataAsync(string? sessionId, bool re
133112
/// </summary>
134113
private async Task SetOrReleaseSessionData(ISessionState? state, CancellationToken cancellationToken)
135114
{
136-
using var req = new HttpRequestMessage { Method = HttpMethod.Put };
115+
using var req = new HttpRequestMessage(HttpMethod.Put, _options.SessionEndpointPath);
137116

138117
if (state is not null)
139118
{
140119
AddSessionCookieToHeader(req, state.SessionID);
141120
req.Content = new SerializedSessionHttpContent(_serializer, state);
142121
}
143122

144-
using var response = await Client.SendAsync(req, cancellationToken);
123+
using var client = _httpClientFactory.CreateClient(RemoteConstants.HttpClientName);
124+
using var response = await client.SendAsync(req, cancellationToken);
145125

146126
LogCommitResponse(response.StatusCode);
147127

src/Services/Authentication/AuthenticationConstants.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ namespace Microsoft.AspNetCore.SystemWebAdapters.Authentication;
55

66
internal static class AuthenticationConstants
77
{
8-
internal const string AuthClientName = "RemoteAuthHttpClient";
9-
108
public const string ForwardedHostHeaderName = "x-forwarded-host";
119
public const string ForwardedProtoHeaderName = "x-forwarded-proto";
1210
public const string MigrationAuthenticateRequestHeaderName = "x-migration-authenticate";

src/Services/RemoteConstants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ namespace Microsoft.AspNetCore.SystemWebAdapters;
66
internal static class RemoteConstants
77
{
88
internal const string ApiKeyHeaderName = "X-SystemWebAdapter-RemoteAppAuthentication-Key";
9+
10+
internal const string HttpClientName = "remote-client";
911
}

src/Services/SessionState/SessionConstants.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ namespace Microsoft.AspNetCore.SystemWebAdapters.SessionState;
55

66
internal static class SessionConstants
77
{
8-
internal const string SessionClientName = "RemoteSessionHttpClient";
9-
108
public const string ReadOnlyHeaderName = "X-SystemWebAdapter-RemoteAppSession-ReadOnly";
119

1210
public const string SessionEndpointPath = "/systemweb-adapters/session";

0 commit comments

Comments
 (0)