Skip to content

Commit 7e48a61

Browse files
authored
Expose building blocks for external service discovery implementations (#6946)
* Expose building blocks for external service discovery implementations * Suppress false positive: string.IsNullOrWhiteSpace is not annotated for nullability in netstandard/netfx
1 parent d02b73e commit 7e48a61

File tree

4 files changed

+86
-56
lines changed

4 files changed

+86
-56
lines changed

src/Libraries/Microsoft.Extensions.ServiceDiscovery.Abstractions/ServiceEndpoint.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics.CodeAnalysis;
45
using System.Net;
56
using Microsoft.AspNetCore.Http.Features;
67
using Microsoft.Extensions.ServiceDiscovery.Internal;
@@ -34,4 +35,52 @@ public static ServiceEndpoint Create(EndPoint endPoint, IFeatureCollection? feat
3435

3536
return new ServiceEndpointImpl(endPoint, features);
3637
}
38+
39+
/// <summary>
40+
/// Tries to convert a specified string representation to its <see cref="ServiceEndpoint"/> equivalent,
41+
/// and returns a value that indicates whether the conversion succeeded.
42+
/// </summary>
43+
/// <param name="value">A string that consists of an IP address or hostname, optionally followed by a colon and port number, or a URI.</param>
44+
/// <param name="serviceEndpoint">When this method returns, contains the equivalent <see cref="ServiceEndpoint"/> if the conversion succeeded; otherwise,
45+
/// <see langword="null"/>. This parameter is passed uninitialized; any value originally supplied will be overwritten.</param>
46+
/// <returns><see langword="true"/> if the string was successfully parsed into a <see cref="ServiceEndpoint"/>; otherwise, <see langword="false"/>.</returns>
47+
public static bool TryParse([NotNullWhen(true)] string? value,
48+
[NotNullWhen(true)] out ServiceEndpoint? serviceEndpoint)
49+
{
50+
EndPoint? endPoint = TryParseEndPoint(value);
51+
52+
if (endPoint != null)
53+
{
54+
serviceEndpoint = Create(endPoint);
55+
return true;
56+
}
57+
else
58+
{
59+
serviceEndpoint = null;
60+
return false;
61+
}
62+
}
63+
64+
private static EndPoint? TryParseEndPoint(string? value)
65+
{
66+
if (!string.IsNullOrWhiteSpace(value))
67+
{
68+
#pragma warning disable CS8602
69+
if (value.IndexOf("://", StringComparison.Ordinal) < 0 && Uri.TryCreate($"fakescheme://{value}", default, out var uri))
70+
#pragma warning restore CS8602
71+
{
72+
var port = uri.Port > 0 ? uri.Port : 0;
73+
return IPAddress.TryParse(uri.Host, out var ip)
74+
? new IPEndPoint(ip, port)
75+
: new DnsEndPoint(uri.Host, port);
76+
}
77+
78+
if (Uri.TryCreate(value, default, out uri))
79+
{
80+
return new UriEndPoint(uri);
81+
}
82+
}
83+
84+
return null;
85+
}
3786
}

src/Libraries/Microsoft.Extensions.ServiceDiscovery/UriEndPoint.cs renamed to src/Libraries/Microsoft.Extensions.ServiceDiscovery.Abstractions/UriEndPoint.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,22 @@ namespace Microsoft.Extensions.ServiceDiscovery;
88
/// <summary>
99
/// An endpoint represented by a <see cref="System.Uri"/>.
1010
/// </summary>
11-
/// <param name="uri">The <see cref="System.Uri"/>.</param>
12-
internal sealed class UriEndPoint(Uri uri) : EndPoint
11+
public class UriEndPoint : EndPoint
1312
{
13+
/// <summary>
14+
/// Creates a new <see cref="UriEndPoint"/>.
15+
/// </summary>
16+
/// <param name="uri">The <see cref="System.Uri"/>.</param>
17+
public UriEndPoint(Uri uri)
18+
{
19+
ArgumentNullException.ThrowIfNull(uri);
20+
Uri = uri;
21+
}
22+
1423
/// <summary>
1524
/// Gets the <see cref="System.Uri"/> associated with this endpoint.
1625
/// </summary>
17-
public Uri Uri => uri;
26+
public Uri Uri { get; }
1827

1928
/// <inheritdoc/>
2029
public override bool Equals(object? obj)
@@ -26,5 +35,5 @@ public override bool Equals(object? obj)
2635
public override int GetHashCode() => Uri.GetHashCode();
2736

2837
/// <inheritdoc/>
29-
public override string? ToString() => uri.ToString();
38+
public override string? ToString() => Uri.ToString();
3039
}

src/Libraries/Microsoft.Extensions.ServiceDiscovery/Configuration/ConfigurationServiceEndpointProvider.cs

Lines changed: 4 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Diagnostics.CodeAnalysis;
5-
using System.Net;
64
using Microsoft.Extensions.Configuration;
75
using Microsoft.Extensions.Logging;
86
using Microsoft.Extensions.Options;
@@ -41,7 +39,8 @@ public ConfigurationServiceEndpointProvider(
4139
_serviceName = query.ServiceName;
4240
_endpointName = query.EndpointName;
4341
_includeAllSchemes = serviceDiscoveryOptions.Value.AllowAllSchemes && query.IncludedSchemes.Count == 0;
44-
_schemes = ServiceDiscoveryOptions.ApplyAllowedSchemes(query.IncludedSchemes, serviceDiscoveryOptions.Value.AllowedSchemes, serviceDiscoveryOptions.Value.AllowAllSchemes);
42+
var allowedSchemes = serviceDiscoveryOptions.Value.ApplyAllowedSchemes(query.IncludedSchemes);
43+
_schemes = allowedSchemes as string[] ?? allowedSchemes.ToArray();
4544
_configuration = configuration;
4645
_logger = logger;
4746
_options = options;
@@ -190,52 +189,18 @@ public ValueTask PopulateAsync(IServiceEndpointBuilder endpoints, CancellationTo
190189

191190
private void AddEndpoint(List<ServiceEndpoint> endpoints, IConfigurationSection section, string endpointName)
192191
{
193-
var value = section.Value;
194-
if (string.IsNullOrWhiteSpace(value) || !TryParseEndPoint(value, out var endPoint))
192+
if (!ServiceEndpoint.TryParse(section.Value, out var serviceEndpoint))
195193
{
196194
throw new KeyNotFoundException($"The endpoint configuration section for service '{_serviceName}' endpoint '{endpointName}' has an invalid value with key '{section.Key}'.");
197195
}
198196

199-
endpoints.Add(CreateEndpoint(endPoint));
200-
}
201-
202-
private static bool TryParseEndPoint(string value, [NotNullWhen(true)] out EndPoint? endPoint)
203-
{
204-
if (value.IndexOf("://") < 0 && Uri.TryCreate($"fakescheme://{value}", default, out var uri))
205-
{
206-
var port = uri.Port > 0 ? uri.Port : 0;
207-
if (IPAddress.TryParse(uri.Host, out var ip))
208-
{
209-
endPoint = new IPEndPoint(ip, port);
210-
}
211-
else
212-
{
213-
endPoint = new DnsEndPoint(uri.Host, port);
214-
}
215-
}
216-
else if (Uri.TryCreate(value, default, out uri))
217-
{
218-
endPoint = new UriEndPoint(uri);
219-
}
220-
else
221-
{
222-
endPoint = null;
223-
return false;
224-
}
225-
226-
return true;
227-
}
228-
229-
private ServiceEndpoint CreateEndpoint(EndPoint endPoint)
230-
{
231-
var serviceEndpoint = ServiceEndpoint.Create(endPoint);
232197
serviceEndpoint.Features.Set<IServiceEndpointProvider>(this);
233198
if (_options.Value.ShouldApplyHostNameMetadata(serviceEndpoint))
234199
{
235200
serviceEndpoint.Features.Set<IHostNameFeature>(this);
236201
}
237202

238-
return serviceEndpoint;
203+
endpoints.Add(serviceEndpoint);
239204
}
240205

241206
public override string ToString() => "Configuration";

src/Libraries/Microsoft.Extensions.ServiceDiscovery/ServiceDiscoveryOptions.cs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Collections.ObjectModel;
45
using Microsoft.Extensions.Primitives;
56

67
namespace Microsoft.Extensions.ServiceDiscovery;
@@ -30,24 +31,30 @@ public sealed class ServiceDiscoveryOptions
3031
/// </remarks>
3132
public IList<string> AllowedSchemes { get; set; } = new List<string>();
3233

33-
internal static string[] ApplyAllowedSchemes(IReadOnlyList<string> schemes, IList<string> allowedSchemes, bool allowAllSchemes)
34+
/// <summary>
35+
/// Filters the specified URI schemes to include only those that are applicable, based on the current settings.
36+
/// </summary>
37+
/// <param name="requestedSchemes">The URI schemes to be evaluated against the allowed schemes.</param>
38+
/// <returns>
39+
/// The URI schemes that are applicable. If no schemes are requested, all allowed schemes are returned.
40+
/// If all schemes are allowed, only the requested schemes are returned.
41+
/// Otherwise, the intersection of requested and allowed schemes is returned.
42+
/// </returns>
43+
public IReadOnlyList<string> ApplyAllowedSchemes(IReadOnlyList<string> requestedSchemes)
3444
{
35-
if (schemes.Count > 0)
45+
ArgumentNullException.ThrowIfNull(requestedSchemes);
46+
47+
if (requestedSchemes.Count > 0)
3648
{
37-
if (allowAllSchemes)
49+
if (AllowAllSchemes)
3850
{
39-
if (schemes is string[] array && array.Length > 0)
40-
{
41-
return array;
42-
}
43-
44-
return schemes.ToArray();
51+
return requestedSchemes;
4552
}
4653

4754
List<string> result = [];
48-
foreach (var s in schemes)
55+
foreach (var s in requestedSchemes)
4956
{
50-
foreach (var allowed in allowedSchemes)
57+
foreach (var allowed in AllowedSchemes)
5158
{
5259
if (string.Equals(s, allowed, StringComparison.OrdinalIgnoreCase))
5360
{
@@ -57,10 +64,10 @@ internal static string[] ApplyAllowedSchemes(IReadOnlyList<string> schemes, ILis
5764
}
5865
}
5966

60-
return result.ToArray();
67+
return result.AsReadOnly();
6168
}
6269

6370
// If no schemes were specified, but a set of allowed schemes were specified, allow those.
64-
return allowedSchemes.ToArray();
71+
return new ReadOnlyCollection<string>(AllowedSchemes);
6572
}
6673
}

0 commit comments

Comments
 (0)