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()); } } }