using System;
using System.Collections.Generic;
using System.Linq;
using Best.HTTP.HostSetting;
namespace Best.HTTP.Hosts.Settings
{
/**
* Host Settings Hierarchy for the following hosts, settings are stored as leafs:
*
* *.com
* *.example.com
* example.com
*
* '*' matches one or more subdomains so *.example.com
* - matches a.example.com and a.b.example.com
* - but doesn't match example.com!
*
*
*
* [com] [localhost] [org] [*]
* +------+------+ | | |
* | | [setting] [*] [setting]
* [example] [*] |
* / \ | [setting]
* [b] [setting] [setting]
* |
* [a]
* |
* [setting]
* */
///
/// Manages host-specific settings for HTTP requests based on hostnames.
/// The HostSettingsManager is a powerful tool for fine-tuning HTTP request and connection behaviors
/// on a per-host basis. It enables you to define custom settings for specific hostnames
/// while maintaining default settings for all other hosts. This level of granularity allows you to
/// optimize and customize HTTP requests for different endpoints within your application.
///
///
/// When host-specific settings are not found for a given host variant, the default
/// associated with the "*" host will be returned.
///
public sealed class HostSettingsManager
{
SortedList _rootNodes = new SortedList(AsteriskStringComparer.Instance);
///
/// Initializes a new instance of the class with default settings for all hosts ("*").
///
public HostSettingsManager() => Add("*", new HostSettings());
///
/// Adds default settings for the host part of the specified URI. This is equivalent to calling with the a new .
///
/// The URI for which default settings should be applied. Only the host part of the URI will be used.
/// A instance with default values.
public HostSettings AddDefault(Uri uri) => Add(uri, new HostSettings());
///
/// Adds default settings for the the specified host name. This is equivalent to calling with the a new .
///
/// The hostname for which default settings should be applied.
/// A instance with default values.
public HostSettings AddDefault(string hostname) => Add(hostname, new HostSettings());
///
/// Adds host-specific settings for the host part of the specified URI.
///
/// The URI for which settings should be applied. Only the host part of the URI will be used.
/// The to apply.
public HostSettings Add(Uri uri, HostSettings settings) => Add(uri.Host, settings);
///
/// Adds host-specific settings for the specified hostname.
///
/// The hostname for which settings should be applied.
/// The to apply.
/// Thrown when either the hostname or settings is null.
/// Thrown when the hostname contains more than one asterisk ('*').
public HostSettings Add(string hostname, HostSettings settings)
{
if (string.IsNullOrEmpty(hostname))
throw new ArgumentNullException(nameof(hostname));
if (settings == null)
throw new ArgumentNullException(nameof(settings));
if (hostname.IndexOf('*') != hostname.LastIndexOf('*'))
throw new FormatException($"{nameof(hostname)} (\"{hostname}\") MUST contain only one '*'!");
// From "a.b.example.com" create a list: [ "com", "example", "b", "a"]
var segments = hostname.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries).Reverse().ToList();
string subKey = segments[0];
segments.RemoveAt(0);
if (!_rootNodes.TryGetValue(subKey, out var node))
_rootNodes.Add(subKey, node = new Node(subKey, null));
node.Add(segments, settings);
return settings;
}
///
/// Gets for the host part of the specified . Returns the default settings associated with "*" when not found.
///
/// The for which settings should be retrieved. Only the host part of the variant will be used.
/// The host settings for the specified host variant or the default settings for "*" if not found.
public HostSettings Get(HostVariant variant, bool fallbackToWildcard = true) => Get(variant.Host, fallbackToWildcard);
///
/// Gets for the host part of the specified . Returns the default settings associated with "*" when not found.
///
/// The for which settings should be retrieved. Only the host part of the host key will be used.
/// The host settings for the specified host key or the default settings for "*" if not found.
public HostSettings Get(HostKey hostKey, bool fallbackToWildcard = true) => Get(hostKey.Host, fallbackToWildcard);
///
/// Gets for the host part of the specified . Returns the default settings associated with "*" when not found.
///
/// The for which settings should be retrieved. Only the host part of the URI will be used.
/// The host settings for the specified URI or the default settings for "*" if not found.
public HostSettings Get(Uri uri, bool fallbackToWildcard = true) => Get(uri.Host, fallbackToWildcard);
///
/// Gets for the host part of the specified hostname. Returns the default settings associated with "*" when not found.
///
/// The hostname for which settings should be retrieved. Only the host part of the hostname will be used.
/// The host settings for the specified hostname or the default settings for "*" if not found.
/// Thrown when the hostname is null.
public HostSettings Get(string hostname, bool fallbackToWildcard = true)
{
if (string.IsNullOrEmpty(hostname))
throw new ArgumentNullException(nameof(hostname));
// This splits the hostname (a.b.c.tld) into segments (["a", "b", "c", "tld"]), reverse it (["tld", "c", "b", "a"])
// and creates a final List object.
var segments = hostname.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries)
.Reverse()
.ToList();
string subKey = segments[0];
segments.RemoveAt(0);
HostSettings foundSettings = null;
if (_rootNodes.TryGetValue(subKey, out var node))
foundSettings = node.Find(segments);
if (fallbackToWildcard && foundSettings == null && _rootNodes.TryGetValue("*", out var asteriskNode))
foundSettings = asteriskNode.hostSettings;
return foundSettings;
}
///
/// Clears all host-specific settings and resetting the default ("*") with default values.
///
public void Clear()
{
_rootNodes.Clear();
Add("*", new HostSettings());
}
}
}