Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/aspnetcore.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Newton-King <james@newtonking.com>2022-11-13 11:40:43 +0300
committerJames Newton-King <james@newtonking.com>2022-11-13 11:40:43 +0300
commitd5911f9918cba5ba3ba9751a8d2b10ad90f00399 (patch)
tree672f7b050f784153d305322db03904ca8353426e
parent2803ff75e0c3695ed786f26da306aadcc409e74f (diff)
Support named pipe from urls argumentjamesnk/namedpipes-transport
-rw-r--r--src/Http/Http/src/BindingAddress.cs52
-rw-r--r--src/Http/Http/src/PublicAPI.Unshipped.txt2
-rw-r--r--src/Servers/Connections.Abstractions/src/NamedPipeEndPoint.cs3
-rw-r--r--src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs4
-rw-r--r--src/Servers/Kestrel/Core/src/ListenOptions.cs4
-rw-r--r--src/Servers/Kestrel/Core/test/AddressBinderTests.cs25
-rw-r--r--src/Servers/Kestrel/Transport.NamedPipes/test/WebHostTests.cs51
7 files changed, 132 insertions, 9 deletions
diff --git a/src/Http/Http/src/BindingAddress.cs b/src/Http/Http/src/BindingAddress.cs
index 398489c84d..6e6e12b46a 100644
--- a/src/Http/Http/src/BindingAddress.cs
+++ b/src/Http/Http/src/BindingAddress.cs
@@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Http;
public class BindingAddress
{
private const string UnixPipeHostPrefix = "unix:/";
+ private const string NamedPipeHostPrefix = "pipe:";
private BindingAddress(string host, string pathBase, int port, string scheme)
{
@@ -58,6 +59,14 @@ public class BindingAddress
public bool IsUnixPipe => Host.StartsWith(UnixPipeHostPrefix, StringComparison.Ordinal);
/// <summary>
+ /// Gets a value that determines if this instance represents a named pipe.
+ /// <para>
+ /// Returns <see langword="true"/> if <see cref="Host"/> starts with <c>pipe:</c> prefix.
+ /// </para>
+ /// </summary>
+ public bool IsNamedPipe => Host.StartsWith(NamedPipeHostPrefix, StringComparison.Ordinal);
+
+ /// <summary>
/// Gets the unix pipe path if this instance represents a Unix pipe.
/// </summary>
public string UnixPipePath
@@ -73,6 +82,22 @@ public class BindingAddress
}
}
+ /// <summary>
+ /// Gets the named pipe path if this instance represents a named pipe.
+ /// </summary>
+ public string NamedPipePath
+ {
+ get
+ {
+ if (!IsNamedPipe)
+ {
+ throw new InvalidOperationException("Binding address is not a named pipe.");
+ }
+
+ return GetNamedPipePath(Host);
+ }
+ }
+
private static string GetUnixPipePath(string host)
{
var unixPipeHostPrefixLength = UnixPipeHostPrefix.Length;
@@ -84,10 +109,12 @@ public class BindingAddress
return host.Substring(unixPipeHostPrefixLength);
}
+ private static string GetNamedPipePath(string host) => host.Substring(NamedPipeHostPrefix.Length);
+
/// <inheritdoc />
public override string ToString()
{
- if (IsUnixPipe)
+ if (IsUnixPipe || IsNamedPipe)
{
return Scheme.ToLowerInvariant() + Uri.SchemeDelimiter + Host.ToLowerInvariant();
}
@@ -135,15 +162,11 @@ public class BindingAddress
var schemeDelimiterEnd = schemeDelimiterStart + Uri.SchemeDelimiter.Length;
var isUnixPipe = address.IndexOf(UnixPipeHostPrefix, schemeDelimiterEnd, StringComparison.Ordinal) == schemeDelimiterEnd;
+ var isNamedPipe = address.IndexOf(NamedPipeHostPrefix, schemeDelimiterEnd, StringComparison.Ordinal) == schemeDelimiterEnd;
int pathDelimiterStart;
int pathDelimiterEnd;
- if (!isUnixPipe)
- {
- pathDelimiterStart = address.IndexOf("/", schemeDelimiterEnd, StringComparison.Ordinal);
- pathDelimiterEnd = pathDelimiterStart;
- }
- else
+ if (isUnixPipe)
{
var unixPipeHostPrefixLength = UnixPipeHostPrefix.Length;
if (OperatingSystem.IsWindows())
@@ -159,6 +182,16 @@ public class BindingAddress
pathDelimiterStart = address.IndexOf(":", schemeDelimiterEnd + unixPipeHostPrefixLength, StringComparison.Ordinal);
pathDelimiterEnd = pathDelimiterStart + ":".Length;
}
+ else if (isNamedPipe)
+ {
+ pathDelimiterStart = address.IndexOf(":", schemeDelimiterEnd + NamedPipeHostPrefix.Length, StringComparison.Ordinal);
+ pathDelimiterEnd = pathDelimiterStart + ":".Length;
+ }
+ else
+ {
+ pathDelimiterStart = address.IndexOf("/", schemeDelimiterEnd, StringComparison.Ordinal);
+ pathDelimiterEnd = pathDelimiterStart;
+ }
if (pathDelimiterStart < 0)
{
@@ -215,6 +248,11 @@ public class BindingAddress
throw new FormatException($"Invalid url, unix socket path must be absolute: '{address}'");
}
+ if (isNamedPipe && GetNamedPipePath(host).Contains('\\'))
+ {
+ throw new FormatException($"Invalid url, pipe name must not contain backslashes: '{address}'");
+ }
+
string pathBase;
if (address[address.Length - 1] == '/')
{
diff --git a/src/Http/Http/src/PublicAPI.Unshipped.txt b/src/Http/Http/src/PublicAPI.Unshipped.txt
index a158cc48ca..82a37c76f8 100644
--- a/src/Http/Http/src/PublicAPI.Unshipped.txt
+++ b/src/Http/Http/src/PublicAPI.Unshipped.txt
@@ -1,3 +1,5 @@
#nullable enable
*REMOVED*Microsoft.AspNetCore.Http.StreamResponseBodyFeature.StreamResponseBodyFeature(System.IO.Stream! stream, Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature! priorFeature) -> void
+Microsoft.AspNetCore.Http.BindingAddress.IsNamedPipe.get -> bool
+Microsoft.AspNetCore.Http.BindingAddress.NamedPipePath.get -> string!
Microsoft.AspNetCore.Http.StreamResponseBodyFeature.StreamResponseBodyFeature(System.IO.Stream! stream, Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature? priorFeature) -> void
diff --git a/src/Servers/Connections.Abstractions/src/NamedPipeEndPoint.cs b/src/Servers/Connections.Abstractions/src/NamedPipeEndPoint.cs
index 31ef3cffc0..52d2d803e8 100644
--- a/src/Servers/Connections.Abstractions/src/NamedPipeEndPoint.cs
+++ b/src/Servers/Connections.Abstractions/src/NamedPipeEndPoint.cs
@@ -47,7 +47,8 @@ public sealed class NamedPipeEndPoint : EndPoint
/// </summary>
public override string ToString()
{
- return $"pipe:{ServerName}/{PipeName}";
+ // Based on format at https://learn.microsoft.com/windows/win32/ipc/pipe-names
+ return $@"\\{ServerName}\pipe\{PipeName}";
}
/// <inheritdoc/>
diff --git a/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs b/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs
index e8f8bbee90..448f845821 100644
--- a/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs
@@ -120,6 +120,10 @@ internal sealed class AddressBinder
{
options = new ListenOptions(parsedAddress.UnixPipePath);
}
+ else if (parsedAddress.IsNamedPipe)
+ {
+ options = new ListenOptions(new NamedPipeEndPoint(parsedAddress.NamedPipePath));
+ }
else if (string.Equals(parsedAddress.Host, "localhost", StringComparison.OrdinalIgnoreCase))
{
// "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs
index 2a028a4954..d3b023e489 100644
--- a/src/Servers/Kestrel/Core/src/ListenOptions.cs
+++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs
@@ -72,7 +72,7 @@ public class ListenOptions : IConnectionBuilder, IMultiplexedConnectionBuilder
/// <remarks>
/// Only set if the <see cref="ListenOptions"/> is bound to a <see cref="NamedPipeEndPoint"/>.
/// </remarks>
- public string? PipeName => (EndPoint as NamedPipeEndPoint)?.ToString();
+ public string? PipeName => (EndPoint as NamedPipeEndPoint)?.PipeName.ToString();
/// <summary>
/// Gets the bound file descriptor to a socket.
@@ -137,6 +137,8 @@ public class ListenOptions : IConnectionBuilder, IMultiplexedConnectionBuilder
{
case UnixDomainSocketEndPoint _:
return $"{Scheme}://unix:{EndPoint}";
+ case NamedPipeEndPoint namedPipeEndPoint:
+ return $"{Scheme}://pipe:{namedPipeEndPoint.PipeName}";
case FileHandleEndPoint _:
return $"{Scheme}://<file handle>";
default:
diff --git a/src/Servers/Kestrel/Core/test/AddressBinderTests.cs b/src/Servers/Kestrel/Core/test/AddressBinderTests.cs
index c63d207f8a..0a4bfb51e0 100644
--- a/src/Servers/Kestrel/Core/test/AddressBinderTests.cs
+++ b/src/Servers/Kestrel/Core/test/AddressBinderTests.cs
@@ -78,6 +78,31 @@ public class AddressBinderTests
Assert.False(https);
}
+ [Fact]
+ public void ParseAddressNamedPipe()
+ {
+ var listenOptions = AddressBinder.ParseAddress("http://pipe:HelloWorld", out var https);
+ Assert.IsType<NamedPipeEndPoint>(listenOptions.EndPoint);
+ Assert.Equal("HelloWorld", listenOptions.PipeName);
+ Assert.False(https);
+ }
+
+ [Fact]
+ public void ParseAddressNamedPipe_ForwardSlashes()
+ {
+ var listenOptions = AddressBinder.ParseAddress("http://pipe:/tmp/kestrel-test.sock", out var https);
+ Assert.IsType<NamedPipeEndPoint>(listenOptions.EndPoint);
+ Assert.Equal("/tmp/kestrel-test.sock", listenOptions.PipeName);
+ Assert.False(https);
+ }
+
+ [Fact]
+ public void ParseAddressNamedPipe_ErrorFromBackslash()
+ {
+ var ex = Assert.Throws<FormatException>(() => AddressBinder.ParseAddress(@"http://pipe:this\is\invalid", out var https));
+ Assert.Equal(@"Invalid url, pipe name must not contain backslashes: 'http://pipe:this\is\invalid'", ex.Message);
+ }
+
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Windows, SkipReason = "tmp/kestrel-test.sock is not valid for windows. Unix socket path must be absolute.")]
public void ParseAddressUnixPipe()
diff --git a/src/Servers/Kestrel/Transport.NamedPipes/test/WebHostTests.cs b/src/Servers/Kestrel/Transport.NamedPipes/test/WebHostTests.cs
index c4ee759c79..5f330673ae 100644
--- a/src/Servers/Kestrel/Transport.NamedPipes/test/WebHostTests.cs
+++ b/src/Servers/Kestrel/Transport.NamedPipes/test/WebHostTests.cs
@@ -281,6 +281,57 @@ public class WebHostTests : LoggedTest
}
}
+ [Fact]
+ public async Task ListenNamedPipeEndpoint_FromUrl_HelloWorld_ClientSuccess()
+ {
+ // Arrange
+ using var httpEventSource = new HttpEventSourceListener(LoggerFactory);
+ var pipeName = NamedPipeTestHelpers.GetUniquePipeName();
+ var url = $"http://pipe:{pipeName}";
+
+ var builder = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .UseUrls(url)
+ .UseKestrel()
+ .Configure(app =>
+ {
+ app.Run(async context =>
+ {
+ await context.Response.WriteAsync("hello, world");
+ });
+ });
+ })
+ .ConfigureServices(AddTestLogging);
+
+ using (var host = builder.Build())
+ using (var client = CreateClient(pipeName))
+ {
+ await host.StartAsync().DefaultTimeout();
+
+ var request = new HttpRequestMessage(HttpMethod.Get, $"http://127.0.0.1/")
+ {
+ Version = HttpVersion.Version11,
+ VersionPolicy = HttpVersionPolicy.RequestVersionExact
+ };
+
+ // Act
+ var response = await client.SendAsync(request).DefaultTimeout();
+
+ // Assert
+ response.EnsureSuccessStatusCode();
+ Assert.Equal(HttpVersion.Version11, response.Version);
+ var responseText = await response.Content.ReadAsStringAsync().DefaultTimeout();
+ Assert.Equal("hello, world", responseText);
+
+ await host.StopAsync().DefaultTimeout();
+ }
+
+ var listeningOn = TestSink.Writes.Single(m => m.EventId.Name == "ListeningOnAddress");
+ Assert.Equal($"Now listening on: {url}", listeningOn.Message);
+ }
+
private static HttpClient CreateClient(string pipeName, TokenImpersonationLevel? impersonationLevel = null)
{
var httpHandler = new SocketsHttpHandler