Files
SocialPub/PrivaPub/Infrastructure/Http/EdgeHints.cs
T

55 lines
1.7 KiB
C#
Raw Normal View History

using PrivaPub.Infrastructure.Geo;
using System.Collections.Concurrent;
namespace PrivaPub.Infrastructure.Http
{
public interface IEdgeHints
{
void Remember(string host, CdnProvider cdn);
CdnProvider Of(string host);
}
// The CDN each remote host's responses came through, as their headers tell it (CdnCatalog fingerprints), for the
// weekly description of the server. Like ConnectedAddresses, memory only: what a host answers is never stored per
// request.
public class EdgeHints : IEdgeHints
{
const int MaxHosts = 50_000;
readonly ConcurrentDictionary<string, CdnProvider> _hints = new(StringComparer.OrdinalIgnoreCase);
public void Remember(string host, CdnProvider cdn)
{
if (string.IsNullOrEmpty(host))
return;
if (cdn == default)
{
_hints.TryRemove(host, out _);
return;
}
if (_hints.Count >= MaxHosts)
_hints.Clear();
_hints[host] = cdn;
}
public CdnProvider Of(string host) => host != default && _hints.TryGetValue(host, out var cdn) ? cdn : default;
}
// On the federation client: reads each response's CDN fingerprint for its host. A response without one forgets the
// host's hint, so a server that leaves its CDN is seen as itself again.
public class EdgeHintsHandler : DelegatingHandler
{
readonly IEdgeHints _hints;
public EdgeHintsHandler(IEdgeHints hints) => _hints = hints;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (request.RequestUri is { } uri && (int)response.StatusCode is >= 200 and < 500)
_hints.Remember(uri.Host, CdnCatalog.FromResponse(response));
return response;
}
}
}