diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Abstractions/ServiceEndpoint.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Abstractions/ServiceEndpoint.cs index 33e0eff4d69..e5a10374adc 100644 --- a/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Abstractions/ServiceEndpoint.cs +++ b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Abstractions/ServiceEndpoint.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.Net; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.ServiceDiscovery.Internal; @@ -34,4 +35,52 @@ public static ServiceEndpoint Create(EndPoint endPoint, IFeatureCollection? feat return new ServiceEndpointImpl(endPoint, features); } + + /// + /// Tries to convert a specified string representation to its equivalent, + /// and returns a value that indicates whether the conversion succeeded. + /// + /// A string that consists of an IP address or hostname, optionally followed by a colon and port number, or a URI. + /// When this method returns, contains the equivalent if the conversion succeeded; otherwise, + /// . This parameter is passed uninitialized; any value originally supplied will be overwritten. + /// if the string was successfully parsed into a ; otherwise, . + public static bool TryParse([NotNullWhen(true)] string? value, + [NotNullWhen(true)] out ServiceEndpoint? serviceEndpoint) + { + EndPoint? endPoint = TryParseEndPoint(value); + + if (endPoint != null) + { + serviceEndpoint = Create(endPoint); + return true; + } + else + { + serviceEndpoint = null; + return false; + } + } + + private static EndPoint? TryParseEndPoint(string? value) + { + if (!string.IsNullOrWhiteSpace(value)) + { +#pragma warning disable CS8602 + if (value.IndexOf("://", StringComparison.Ordinal) < 0 && Uri.TryCreate($"fakescheme://{value}", default, out var uri)) +#pragma warning restore CS8602 + { + var port = uri.Port > 0 ? uri.Port : 0; + return IPAddress.TryParse(uri.Host, out var ip) + ? new IPEndPoint(ip, port) + : new DnsEndPoint(uri.Host, port); + } + + if (Uri.TryCreate(value, default, out uri)) + { + return new UriEndPoint(uri); + } + } + + return null; + } } diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery/UriEndPoint.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Abstractions/UriEndPoint.cs similarity index 63% rename from src/Libraries/Microsoft.Extensions.ServiceDiscovery/UriEndPoint.cs rename to src/Libraries/Microsoft.Extensions.ServiceDiscovery.Abstractions/UriEndPoint.cs index 6b5b07d199e..d7bb1ab040e 100644 --- a/src/Libraries/Microsoft.Extensions.ServiceDiscovery/UriEndPoint.cs +++ b/src/Libraries/Microsoft.Extensions.ServiceDiscovery.Abstractions/UriEndPoint.cs @@ -8,13 +8,22 @@ namespace Microsoft.Extensions.ServiceDiscovery; /// /// An endpoint represented by a . /// -/// The . -internal sealed class UriEndPoint(Uri uri) : EndPoint +public class UriEndPoint : EndPoint { + /// + /// Creates a new . + /// + /// The . + public UriEndPoint(Uri uri) + { + ArgumentNullException.ThrowIfNull(uri); + Uri = uri; + } + /// /// Gets the associated with this endpoint. /// - public Uri Uri => uri; + public Uri Uri { get; } /// public override bool Equals(object? obj) @@ -26,5 +35,5 @@ public override bool Equals(object? obj) public override int GetHashCode() => Uri.GetHashCode(); /// - public override string? ToString() => uri.ToString(); + public override string? ToString() => Uri.ToString(); } diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery/Configuration/ConfigurationServiceEndpointProvider.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery/Configuration/ConfigurationServiceEndpointProvider.cs index e8c84b69ec8..72d6e7b6b81 100644 --- a/src/Libraries/Microsoft.Extensions.ServiceDiscovery/Configuration/ConfigurationServiceEndpointProvider.cs +++ b/src/Libraries/Microsoft.Extensions.ServiceDiscovery/Configuration/ConfigurationServiceEndpointProvider.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; -using System.Net; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -41,7 +39,8 @@ public ConfigurationServiceEndpointProvider( _serviceName = query.ServiceName; _endpointName = query.EndpointName; _includeAllSchemes = serviceDiscoveryOptions.Value.AllowAllSchemes && query.IncludedSchemes.Count == 0; - _schemes = ServiceDiscoveryOptions.ApplyAllowedSchemes(query.IncludedSchemes, serviceDiscoveryOptions.Value.AllowedSchemes, serviceDiscoveryOptions.Value.AllowAllSchemes); + var allowedSchemes = serviceDiscoveryOptions.Value.ApplyAllowedSchemes(query.IncludedSchemes); + _schemes = allowedSchemes as string[] ?? allowedSchemes.ToArray(); _configuration = configuration; _logger = logger; _options = options; @@ -190,52 +189,18 @@ public ValueTask PopulateAsync(IServiceEndpointBuilder endpoints, CancellationTo private void AddEndpoint(List endpoints, IConfigurationSection section, string endpointName) { - var value = section.Value; - if (string.IsNullOrWhiteSpace(value) || !TryParseEndPoint(value, out var endPoint)) + if (!ServiceEndpoint.TryParse(section.Value, out var serviceEndpoint)) { throw new KeyNotFoundException($"The endpoint configuration section for service '{_serviceName}' endpoint '{endpointName}' has an invalid value with key '{section.Key}'."); } - endpoints.Add(CreateEndpoint(endPoint)); - } - - private static bool TryParseEndPoint(string value, [NotNullWhen(true)] out EndPoint? endPoint) - { - if (value.IndexOf("://") < 0 && Uri.TryCreate($"fakescheme://{value}", default, out var uri)) - { - var port = uri.Port > 0 ? uri.Port : 0; - if (IPAddress.TryParse(uri.Host, out var ip)) - { - endPoint = new IPEndPoint(ip, port); - } - else - { - endPoint = new DnsEndPoint(uri.Host, port); - } - } - else if (Uri.TryCreate(value, default, out uri)) - { - endPoint = new UriEndPoint(uri); - } - else - { - endPoint = null; - return false; - } - - return true; - } - - private ServiceEndpoint CreateEndpoint(EndPoint endPoint) - { - var serviceEndpoint = ServiceEndpoint.Create(endPoint); serviceEndpoint.Features.Set(this); if (_options.Value.ShouldApplyHostNameMetadata(serviceEndpoint)) { serviceEndpoint.Features.Set(this); } - return serviceEndpoint; + endpoints.Add(serviceEndpoint); } public override string ToString() => "Configuration"; diff --git a/src/Libraries/Microsoft.Extensions.ServiceDiscovery/ServiceDiscoveryOptions.cs b/src/Libraries/Microsoft.Extensions.ServiceDiscovery/ServiceDiscoveryOptions.cs index edc652507d9..a6fd7123aa7 100644 --- a/src/Libraries/Microsoft.Extensions.ServiceDiscovery/ServiceDiscoveryOptions.cs +++ b/src/Libraries/Microsoft.Extensions.ServiceDiscovery/ServiceDiscoveryOptions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.ObjectModel; using Microsoft.Extensions.Primitives; namespace Microsoft.Extensions.ServiceDiscovery; @@ -30,24 +31,30 @@ public sealed class ServiceDiscoveryOptions /// public IList AllowedSchemes { get; set; } = new List(); - internal static string[] ApplyAllowedSchemes(IReadOnlyList schemes, IList allowedSchemes, bool allowAllSchemes) + /// + /// Filters the specified URI schemes to include only those that are applicable, based on the current settings. + /// + /// The URI schemes to be evaluated against the allowed schemes. + /// + /// The URI schemes that are applicable. If no schemes are requested, all allowed schemes are returned. + /// If all schemes are allowed, only the requested schemes are returned. + /// Otherwise, the intersection of requested and allowed schemes is returned. + /// + public IReadOnlyList ApplyAllowedSchemes(IReadOnlyList requestedSchemes) { - if (schemes.Count > 0) + ArgumentNullException.ThrowIfNull(requestedSchemes); + + if (requestedSchemes.Count > 0) { - if (allowAllSchemes) + if (AllowAllSchemes) { - if (schemes is string[] array && array.Length > 0) - { - return array; - } - - return schemes.ToArray(); + return requestedSchemes; } List result = []; - foreach (var s in schemes) + foreach (var s in requestedSchemes) { - foreach (var allowed in allowedSchemes) + foreach (var allowed in AllowedSchemes) { if (string.Equals(s, allowed, StringComparison.OrdinalIgnoreCase)) { @@ -57,10 +64,10 @@ internal static string[] ApplyAllowedSchemes(IReadOnlyList schemes, ILis } } - return result.ToArray(); + return result.AsReadOnly(); } // If no schemes were specified, but a set of allowed schemes were specified, allow those. - return allowedSchemes.ToArray(); + return new ReadOnlyCollection(AllowedSchemes); } }