add all
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Best.HTTP.Hosts.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Moves any added asterisk(*) to the end of the list.
|
||||
/// </summary>
|
||||
[Best.HTTP.Shared.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstruction]
|
||||
internal sealed class AsteriskStringComparer : IComparer<string>
|
||||
{
|
||||
public static readonly AsteriskStringComparer Instance = new AsteriskStringComparer();
|
||||
|
||||
public int Compare(string x, string y)
|
||||
/*{
|
||||
var comparedTo = x.CompareTo(y);
|
||||
|
||||
// Equal?
|
||||
if (comparedTo == 0)
|
||||
return 0;
|
||||
|
||||
return (x, y) switch
|
||||
{
|
||||
("*", _) => 1,
|
||||
(_, "*") => -1,
|
||||
_ => x.CompareTo(y)
|
||||
};
|
||||
}*/
|
||||
=> (x, y) switch
|
||||
{
|
||||
("*", "*") => 0,
|
||||
("*", _) => 1,
|
||||
(_, "*") => -1,
|
||||
_ => x.CompareTo(y)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df72827b7ff4ad04cb70fdad8c347622
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,343 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
using Best.HTTP.Hosts.Connections;
|
||||
using Best.HTTP.HostSetting;
|
||||
using Best.HTTP.Shared.Extensions;
|
||||
using Best.HTTP.Shared.Logger;
|
||||
using Best.HTTP.Shared.PlatformSupport.Network.Tcp;
|
||||
|
||||
namespace Best.HTTP.Hosts.Settings
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
/// <summary>
|
||||
/// Delegate for creating a TLS 1.3 client instance.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI of the request.</param>
|
||||
/// <param name="protocols">A list of supported TLS ALPN protocols.</param>
|
||||
/// <param name="context">The logging context for the operation.</param>
|
||||
/// <returns>A TLS 1.3 client instance.</returns>
|
||||
public delegate Best.HTTP.Shared.TLS.AbstractTls13Client TlsClientFactoryDelegate(Uri uri, List<SecureProtocol.Org.BouncyCastle.Tls.ProtocolName> protocols, LoggingContext context);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Settings for HTTP requests.
|
||||
/// </summary>
|
||||
public class HTTRequestSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The timeout for establishing a connection.
|
||||
/// </summary>
|
||||
public TimeSpan ConnectTimeout = TimeSpan.FromSeconds(20);
|
||||
|
||||
/// <summary>
|
||||
/// The maximum time allowed for the request to complete.
|
||||
/// </summary>
|
||||
public TimeSpan RequestTimeout = TimeSpan.MaxValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Settings for HTTP/1 connections.
|
||||
/// </summary>
|
||||
public class HTTP1ConnectionSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether the connection should be open after receiving the response.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If set to <c>true</c>, internal TCP connections will be reused whenever possible.
|
||||
/// If making rare requests to the server, it's recommended to change this to <c>false</c>.
|
||||
/// </remarks>
|
||||
public bool TryToReuseConnections = true;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum time a connection can remain idle before being closed.
|
||||
/// </summary>
|
||||
public TimeSpan MaxConnectionIdleTime = TimeSpan.FromSeconds(20);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the upload thread should use a <see href="https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool">ThreadPool</see> thread instead of creating and using a <see href="https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread">Thread</see>.
|
||||
/// </summary>
|
||||
/// <remarks>The plugin tries to use ThreadPool threads for known short-living uploads like requests without upload body. With <c>ForceUseThreadPool</c> all HTTP/1 requests, including long uploads or downloads can be forced to use ThreadPool threads.</remarks>
|
||||
public bool ForceUseThreadPool;
|
||||
|
||||
}
|
||||
|
||||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for selecting a client certificate.
|
||||
/// </summary>
|
||||
/// <param name="targetHost">The target host.</param>
|
||||
/// <param name="localCertificates">A collection of local certificates.</param>
|
||||
/// <param name="remoteCertificate">The remote certificate.</param>
|
||||
/// <param name="acceptableIssuers">An array of acceptable certificate issuers.</param>
|
||||
/// <returns>The selected X.509 certificate.</returns>
|
||||
public delegate X509Certificate ClientCertificateSelector(string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers);
|
||||
|
||||
/// <summary>
|
||||
/// Available TLS handlers.
|
||||
/// </summary>
|
||||
public enum TLSHandlers
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
/// <summary>
|
||||
/// To use the 3rd party BouncyCastle implementation.
|
||||
/// </summary>
|
||||
BouncyCastle = 0x00,
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// To use .net's SslStream.
|
||||
/// </summary>
|
||||
Framework = 0x01
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Settings for Bouncy Castle TLS.
|
||||
/// </summary>
|
||||
public class BouncyCastleSettings
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
/// <summary>
|
||||
/// Delegate for creating a TLS 1.3 client instance using Bouncy Castle.
|
||||
/// </summary>
|
||||
public TlsClientFactoryDelegate TlsClientFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The default TLS 1.3 client factory.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI of the request.</param>
|
||||
/// <param name="protocols">A list of supported TLS ALPN protocols.</param>
|
||||
/// <param name="context">The logging context for the operation.</param>
|
||||
/// <returns>A TLS 1.3 client instance.</returns>
|
||||
public static Best.HTTP.Shared.TLS.AbstractTls13Client DefaultTlsClientFactory(Uri uri, List<Best.HTTP.SecureProtocol.Org.BouncyCastle.Tls.ProtocolName> protocols, LoggingContext context)
|
||||
{
|
||||
// http://tools.ietf.org/html/rfc3546#section-3.1
|
||||
// -It is RECOMMENDED that clients include an extension of type "server_name" in the client hello whenever they locate a server by a supported name type.
|
||||
// -Literal IPv4 and IPv6 addresses are not permitted in "HostName".
|
||||
|
||||
// User-defined list has a higher priority
|
||||
List<Best.HTTP.SecureProtocol.Org.BouncyCastle.Tls.ServerName> hostNames = null;
|
||||
|
||||
// If there's no user defined one and the host isn't an IP address, add the default one
|
||||
if (!uri.IsHostIsAnIPAddress())
|
||||
{
|
||||
hostNames = new List<Best.HTTP.SecureProtocol.Org.BouncyCastle.Tls.ServerName>(1);
|
||||
hostNames.Add(new Best.HTTP.SecureProtocol.Org.BouncyCastle.Tls.ServerName(0, System.Text.Encoding.UTF8.GetBytes(uri.Host)));
|
||||
}
|
||||
|
||||
return new Best.HTTP.Shared.TLS.DefaultTls13Client(hostNames, protocols, context);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Settings for .NET's SslStream based handler.
|
||||
/// </summary>
|
||||
public class FrameworkTLSSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The supported TLS versions.
|
||||
/// </summary>
|
||||
public System.Security.Authentication.SslProtocols TlsVersions = System.Security.Authentication.SslProtocols.Tls12;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether to check certificate revocation.
|
||||
/// </summary>
|
||||
public bool CheckCertificateRevocation = true;
|
||||
|
||||
/// <summary>
|
||||
/// The default certification validator.
|
||||
/// </summary>
|
||||
public static Func<string, X509Certificate, X509Chain, SslPolicyErrors, bool> DefaultCertificationValidator = (host, certificate, chain, sslPolicyErrors) => true;
|
||||
|
||||
public Func<string, X509Certificate, X509Chain, SslPolicyErrors, bool> CertificationValidator = DefaultCertificationValidator;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for providing a client certificate.
|
||||
/// </summary>
|
||||
public ClientCertificateSelector ClientCertificationProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Settings for TLS.
|
||||
/// </summary>
|
||||
public class TLSSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The selected TLS handler.
|
||||
/// </summary>
|
||||
public TLSHandlers TLSHandler
|
||||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
= TLSHandlers.BouncyCastle;
|
||||
#else
|
||||
= TLSHandlers.Framework;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Settings for Bouncy Castle.
|
||||
/// </summary>
|
||||
public BouncyCastleSettings BouncyCastleSettings = new BouncyCastleSettings();
|
||||
|
||||
/// <summary>
|
||||
/// .NET's SslStream settings.
|
||||
/// </summary>
|
||||
public FrameworkTLSSettings FrameworkTLSSettings = new FrameworkTLSSettings();
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Settings for <see cref="HostSetting.HostVariant"/>s.
|
||||
/// </summary>
|
||||
public class HostVariantSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum number of connections allowed per host variant.
|
||||
/// </summary>
|
||||
public int MaxConnectionPerVariant = 6;
|
||||
|
||||
/// <summary>
|
||||
/// Factor used when calculations are made whether to open a new connection to the server or not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It has an effect on HTTP/2 connections only.
|
||||
/// <para>Higher values (gte <c>1.0f</c>) delay, lower values (lte <c>1.0f</c>) bring forward creation of new connections.</para>
|
||||
/// </remarks>
|
||||
public float MaxAssignedRequestsFactor = 1.2f;
|
||||
|
||||
/// <summary>
|
||||
/// Factory function to generate HostVariant or descendent instances.
|
||||
/// </summary>
|
||||
public Func<HostVariantSettings, HostKey, HostVariant> VariantFactory = (settings, key) => new HostVariant(key);
|
||||
|
||||
/// <summary>
|
||||
/// Factory function to generate custom connection implementations.
|
||||
/// </summary>
|
||||
public Func<HostVariantSettings, HostVariant, ConnectionBase> ConnectionFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the low-level TCP buffer settings for connections.
|
||||
/// </summary>
|
||||
public class LowLevelConnectionSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the TCP write buffer in bytes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Default value is 1 MiB.</para>
|
||||
/// <para>This determines the maximum amount of data that that the <see cref="TCPStreamer"/> class can buffer up if it's already in a write operation.
|
||||
/// Increasing this value can potentially improve write performance, especially for large messages or data streams.
|
||||
/// However, setting it too high might consume a significant amount of memory, especially if there are many active connections.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <value>The size of the TCP write buffer in bytes.</value>
|
||||
public uint TCPWriteBufferSize = 1024 * 1024;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the read buffer in bytes.
|
||||
/// </summary>
|
||||
/// <value>The size of the read buffer in bytes.</value>
|
||||
/// <remarks>
|
||||
/// <para>Default value is 1 MiB.</para>
|
||||
/// <para>This determines the maximum amount of data that low level streams and the <see cref="TCPStreamer"/> can buffer up for consuming by higher level layers.
|
||||
/// Adjusting this value can affect the read performance of the application.
|
||||
/// Like the write buffer, setting this too high might be memory-intensive, especially with many connections.
|
||||
/// It's advised to find a balance that suits the application's needs and resources.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public uint ReadBufferSize = 1024 * 1024;
|
||||
|
||||
/// <summary>
|
||||
/// TCP keepalive settings. Disabled by default.
|
||||
/// </summary>
|
||||
public KeepAliveSettings KeepAlive = new KeepAliveSettings(false, 7200, 75, 9);
|
||||
|
||||
public struct KeepAliveSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Must be true in order to apply the other values.
|
||||
/// </summary>
|
||||
public readonly bool EnableKeepAlive;
|
||||
|
||||
/// <summary>
|
||||
/// When the SO_KEEPALIVE option is enabled, TCP probes a connection that has been idle for some amount of time. The default value for this idle period is 2 hours.
|
||||
/// The TCP_KEEPIDLE option can be used to affect this value for a given socket, and specifies the number of seconds of idle time between keepalive probes.
|
||||
/// </summary>
|
||||
public readonly int IdleSeconds;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the interval between packets that are sent to validate the connection.
|
||||
/// </summary>
|
||||
public readonly int IntervalSeconds;
|
||||
|
||||
/// <summary>
|
||||
/// When the SO_KEEPALIVE option is enabled, TCP probes a connection that has been idle for some amount of time.
|
||||
/// If the remote system does not respond to a keepalive probe, TCP retransmits the probe a certain number of times before a connection is considered to be broken.
|
||||
/// The TCP_KEEPCNT option can be used to affect this value for a given socket, and specifies the maximum number of keepalive probes to be sent.
|
||||
/// </summary>
|
||||
public readonly int ProbeCount;
|
||||
|
||||
public KeepAliveSettings(bool enableKeepAlive = false, int idleSeconds = 7200, int intervalSeconds = 75, int probeCount = 9)
|
||||
{
|
||||
EnableKeepAlive = enableKeepAlive;
|
||||
IdleSeconds = idleSeconds;
|
||||
IntervalSeconds = intervalSeconds;
|
||||
ProbeCount = probeCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains settings that can be associated with a specific host or host variant.
|
||||
/// </summary>
|
||||
public class HostSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the low-level TCP buffer settings for connections associated with the host or host variant.
|
||||
/// </summary>
|
||||
/// <value>The low-level TCP buffer settings.</value>
|
||||
/// <remarks>
|
||||
/// These settings determine the buffer sizes for reading from and writing to TCP connections,
|
||||
/// which can impact performance and memory usage.
|
||||
/// </remarks>
|
||||
public LowLevelConnectionSettings LowLevelConnectionSettings = new LowLevelConnectionSettings();
|
||||
|
||||
/// <summary>
|
||||
/// Settings related to HTTP requests made to this host or host variant.
|
||||
/// </summary>
|
||||
public HTTRequestSettings RequestSettings = new HTTRequestSettings();
|
||||
|
||||
/// <summary>
|
||||
/// Settings related to HTTP/1.x connection behavior.
|
||||
/// </summary>
|
||||
public HTTP1ConnectionSettings HTTP1ConnectionSettings = new HTTP1ConnectionSettings();
|
||||
|
||||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Settings related to TCP Ringmaster used in non-webgl platforms.
|
||||
/// </summary>
|
||||
public TCPRingmasterSettings TCPRingmasterSettings = new TCPRingmasterSettings();
|
||||
|
||||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
/// <summary>
|
||||
/// Settings related to HTTP/2 connection behavior.
|
||||
/// </summary>
|
||||
public Best.HTTP.Hosts.Connections.HTTP2.HTTP2ConnectionSettings HTTP2ConnectionSettings = new Connections.HTTP2.HTTP2ConnectionSettings();
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Settings related to TLS (Transport Layer Security) behavior.
|
||||
/// </summary>
|
||||
public TLSSettings TLSSettings = new TLSSettings();
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Settings related to <see cref="HostSetting.HostVariant"/> behavior.
|
||||
/// </summary>
|
||||
public HostVariantSettings HostVariantSettings = new HostVariantSettings();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 192e9a6509024184c90042a41aec6274
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,194 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using Best.HTTP.HostSetting;
|
||||
using Best.HTTP.Shared.PlatformSupport.Threading;
|
||||
|
||||
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]
|
||||
* */
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When host-specific settings are not found for a given host variant, the default <see cref="HostSettings"/>
|
||||
/// associated with the "*" host will be returned.
|
||||
/// </remarks>
|
||||
public sealed class HostSettingsManager
|
||||
{
|
||||
private const char SPLITTER = '.';
|
||||
|
||||
private SortedList<string, Node> _rootNodes = new SortedList<string, Node>(AsteriskStringComparer.Instance);
|
||||
private List<string> _segments = new List<string>(4);
|
||||
|
||||
private ConcurrentDictionary<string, HostSettings> _cache = new ConcurrentDictionary<string, HostSettings>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HostSettingsManager"/> class with default settings for all hosts ("*").
|
||||
/// </summary>
|
||||
public HostSettingsManager() => Add("*", new HostSettings());
|
||||
|
||||
/// <summary>
|
||||
/// Adds default settings for the host part of the specified URI. This is equivalent to calling <see cref="Add(Uri, HostSettings)"/> with the a new <see cref="HostSettings"/>.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI for which default settings should be applied. Only the host part of the URI will be used.</param>
|
||||
/// <returns>A <see cref="HostSettings"/> instance with default values.</returns>
|
||||
public HostSettings AddDefault(Uri uri) => Add(uri, new HostSettings());
|
||||
|
||||
/// <summary>
|
||||
/// Adds default settings for the the specified host name. This is equivalent to calling <see cref="Add(string, HostSettings)"/> with the a new <see cref="HostSettings"/>.
|
||||
/// </summary>
|
||||
/// <param name="hostname">The hostname for which default settings should be applied.</param>
|
||||
/// <returns>A <see cref="HostSettings"/> instance with default values.</returns>
|
||||
public HostSettings AddDefault(string hostname) => Add(hostname, new HostSettings());
|
||||
|
||||
/// <summary>
|
||||
/// Adds host-specific settings for the host part of the specified URI.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI for which settings should be applied. Only the host part of the URI will be used.</param>
|
||||
/// <param name="settings">The <see cref="HostSettings"/> to apply.</param>
|
||||
public HostSettings Add(Uri uri, HostSettings settings) => Add(uri.Host, settings);
|
||||
|
||||
/// <summary>
|
||||
/// Adds host-specific settings for the specified hostname.
|
||||
/// </summary>
|
||||
/// <param name="hostname">The hostname for which settings should be applied.</param>
|
||||
/// <param name="settings">The <see cref="HostSettings"/> to apply.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when either the hostname or settings is null.</exception>
|
||||
/// <exception cref="FormatException">Thrown when the hostname contains more than one asterisk ('*').</exception>
|
||||
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"]
|
||||
lock(this._segments)
|
||||
{
|
||||
this._segments.Clear();
|
||||
this._segments.AddRange(hostname.Split(SPLITTER, StringSplitOptions.RemoveEmptyEntries));
|
||||
this._segments.Reverse();
|
||||
|
||||
string subKey = this._segments[0];
|
||||
this._segments.RemoveAt(0);
|
||||
|
||||
if (!_rootNodes.TryGetValue(subKey, out var node))
|
||||
_rootNodes.Add(subKey, node = new Node(subKey, null));
|
||||
|
||||
node.Add(this._segments, settings);
|
||||
|
||||
this._cache.Clear();
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets <see cref="HostSettings"/> for the host part of the specified <see cref="HostVariant"/>. Returns the default settings associated with "*" when not found.
|
||||
/// </summary>
|
||||
/// <param name="variant">The <see cref="HostVariant"/> for which settings should be retrieved. Only the host part of the variant will be used.</param>
|
||||
/// <returns>The host settings for the specified host variant or the default settings for "*" if not found.</returns>
|
||||
public HostSettings Get(HostVariant variant, bool fallbackToWildcard = true) => Get(variant.Host, fallbackToWildcard);
|
||||
|
||||
/// <summary>
|
||||
/// Gets <see cref="HostSettings"/> for the host part of the specified <see cref="HostKey"/>. Returns the default settings associated with "*" when not found.
|
||||
/// </summary>
|
||||
/// <param name="hostKey">The <see cref="HostKey"/> for which settings should be retrieved. Only the host part of the host key will be used.</param>
|
||||
/// <returns>The host settings for the specified host key or the default settings for "*" if not found.</returns>
|
||||
public HostSettings Get(HostKey hostKey, bool fallbackToWildcard = true) => Get(hostKey.Host, fallbackToWildcard);
|
||||
|
||||
/// <summary>
|
||||
/// Gets <see cref="HostSettings"/> for the host part of the specified <see cref="Uri"/>. Returns the default settings associated with "*" when not found.
|
||||
/// </summary>
|
||||
/// <param name="uri">The <see cref="Uri"/> for which settings should be retrieved. Only the host part of the URI will be used.</param>
|
||||
/// <returns>The host settings for the specified URI or the default settings for "*" if not found.</returns>
|
||||
public HostSettings Get(Uri uri, bool fallbackToWildcard = true) => Get(uri.Host, fallbackToWildcard);
|
||||
|
||||
/// <summary>
|
||||
/// Gets <see cref="HostSettings"/> for the host part of the specified hostname. Returns the default settings associated with "*" when not found.
|
||||
/// </summary>
|
||||
/// <param name="hostname">The hostname for which settings should be retrieved. Only the host part of the hostname will be used.</param>
|
||||
/// <returns>The host settings for the specified hostname or the default settings for "*" if not found.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the hostname is null.</exception>
|
||||
public HostSettings Get(string hostname, bool fallbackToWildcard = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hostname))
|
||||
throw new ArgumentNullException(nameof(hostname));
|
||||
|
||||
HostSettings foundSettings = null;
|
||||
|
||||
if (this._cache.TryGetValue(hostname, out foundSettings))
|
||||
return foundSettings;
|
||||
|
||||
lock (this._segments)
|
||||
{
|
||||
// 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<string> object.
|
||||
this._segments.Clear();
|
||||
this._segments.AddRange(hostname.Split(SPLITTER, StringSplitOptions.RemoveEmptyEntries));
|
||||
this._segments.Reverse();
|
||||
|
||||
string subKey = this._segments[0];
|
||||
this._segments.RemoveAt(0);
|
||||
|
||||
if (_rootNodes.TryGetValue(subKey, out var node))
|
||||
foundSettings = node.Find(this._segments);
|
||||
}
|
||||
|
||||
if (fallbackToWildcard && foundSettings == null && _rootNodes.TryGetValue("*", out var asteriskNode))
|
||||
foundSettings = asteriskNode.hostSettings;
|
||||
|
||||
if (foundSettings != null)
|
||||
this._cache.AddOrUpdate(hostname, foundSettings, (key, settings) => foundSettings);
|
||||
|
||||
return foundSettings;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Clears all host-specific settings and resetting the default ("*") with default values.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_rootNodes.Clear();
|
||||
_cache.Clear();
|
||||
Add("*", new HostSettings());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a86d4c63257f0e240a0051293ea70e54
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Best.HTTP.Hosts.Settings
|
||||
{
|
||||
internal sealed class Node
|
||||
{
|
||||
public string key;
|
||||
public SortedList<string, Node> childNodes;
|
||||
public HostSettings hostSettings;
|
||||
|
||||
public Node(string key) => this.key = key;
|
||||
public Node(string key, HostSettings settings) : this(key) => this.hostSettings = settings;
|
||||
|
||||
public void Add(string subKey, Node subNode)
|
||||
{
|
||||
if (childNodes == null)
|
||||
childNodes = new SortedList<string, Node>(AsteriskStringComparer.Instance);
|
||||
|
||||
childNodes.Add(subKey, subNode);
|
||||
}
|
||||
|
||||
public void AddSetting(HostSettings settings)
|
||||
{
|
||||
this.hostSettings = settings;
|
||||
}
|
||||
|
||||
public void Add(List<string> segments, HostSettings settings)
|
||||
{
|
||||
if (segments.Count == 0)
|
||||
{
|
||||
this.hostSettings = settings;
|
||||
return;
|
||||
}
|
||||
|
||||
string subKey = segments[0];
|
||||
segments.RemoveAt(0);
|
||||
|
||||
if (this.childNodes == null)
|
||||
this.childNodes = new SortedList<string, Node>(AsteriskStringComparer.Instance);
|
||||
|
||||
if (!this.childNodes.TryGetValue(subKey, out var node))
|
||||
this.childNodes.Add(subKey, node = new Node(subKey, null));
|
||||
|
||||
node.Add(segments, settings);
|
||||
}
|
||||
|
||||
public HostSettings Find(List<string> segments)
|
||||
{
|
||||
if (segments.Count == 0)
|
||||
return this.hostSettings;
|
||||
|
||||
if (this.childNodes == null || this.childNodes.Count == 0)
|
||||
return null;
|
||||
|
||||
string subKey = segments[0];
|
||||
segments.RemoveAt(0);
|
||||
|
||||
if (this.childNodes.TryGetValue(subKey, out var node))
|
||||
return node.Find(segments);
|
||||
|
||||
if (this.childNodes.TryGetValue("*", out var wildcardNode))
|
||||
return wildcardNode.Find(segments);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 30f0ce3004e4fb24993e48a1ed91de02
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user