Skip to content

Commit dfd4cde

Browse files
authored
Add Kestrel named pipes transport (#44426)
1 parent 8a82f8c commit dfd4cde

40 files changed

+1897
-39
lines changed

AspNetCore.sln

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,6 +1756,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "perf", "perf", "{74377D3E-E
17561756
EndProject
17571757
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.HttpSys.Microbenchmarks", "src\Servers\HttpSys\perf\Microbenchmarks\Microsoft.AspNetCore.Server.HttpSys.Microbenchmarks.csproj", "{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}"
17581758
EndProject
1759+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Transport.NamedPipes", "Transport.NamedPipes", "{F057512B-55BF-4A8B-A027-A0505F8BA10C}"
1760+
EndProject
1761+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes", "src\Servers\Kestrel\Transport.NamedPipes\src\Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.csproj", "{10173568-A65E-44E5-8C6F-4AA49D0577A1}"
1762+
EndProject
1763+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Tests", "src\Servers\Kestrel\Transport.NamedPipes\test\Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Tests.csproj", "{97C7D2A4-87E5-4A4A-A170-D736427D5C21}"
1764+
EndProject
17591765
Global
17601766
GlobalSection(SolutionConfigurationPlatforms) = preSolution
17611767
Debug|Any CPU = Debug|Any CPU
@@ -10533,6 +10539,38 @@ Global
1053310539
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Release|x64.Build.0 = Release|Any CPU
1053410540
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Release|x86.ActiveCfg = Release|Any CPU
1053510541
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Release|x86.Build.0 = Release|Any CPU
10542+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10543+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
10544+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|arm64.ActiveCfg = Debug|Any CPU
10545+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|arm64.Build.0 = Debug|Any CPU
10546+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|x64.ActiveCfg = Debug|Any CPU
10547+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|x64.Build.0 = Debug|Any CPU
10548+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|x86.ActiveCfg = Debug|Any CPU
10549+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|x86.Build.0 = Debug|Any CPU
10550+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
10551+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|Any CPU.Build.0 = Release|Any CPU
10552+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|arm64.ActiveCfg = Release|Any CPU
10553+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|arm64.Build.0 = Release|Any CPU
10554+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|x64.ActiveCfg = Release|Any CPU
10555+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|x64.Build.0 = Release|Any CPU
10556+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|x86.ActiveCfg = Release|Any CPU
10557+
{10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|x86.Build.0 = Release|Any CPU
10558+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10559+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|Any CPU.Build.0 = Debug|Any CPU
10560+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|arm64.ActiveCfg = Debug|Any CPU
10561+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|arm64.Build.0 = Debug|Any CPU
10562+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|x64.ActiveCfg = Debug|Any CPU
10563+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|x64.Build.0 = Debug|Any CPU
10564+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|x86.ActiveCfg = Debug|Any CPU
10565+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|x86.Build.0 = Debug|Any CPU
10566+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|Any CPU.ActiveCfg = Release|Any CPU
10567+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|Any CPU.Build.0 = Release|Any CPU
10568+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|arm64.ActiveCfg = Release|Any CPU
10569+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|arm64.Build.0 = Release|Any CPU
10570+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|x64.ActiveCfg = Release|Any CPU
10571+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|x64.Build.0 = Release|Any CPU
10572+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|x86.ActiveCfg = Release|Any CPU
10573+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|x86.Build.0 = Release|Any CPU
1053610574
EndGlobalSection
1053710575
GlobalSection(SolutionProperties) = preSolution
1053810576
HideSolutionNode = FALSE
@@ -11400,6 +11438,9 @@ Global
1140011438
{F43CC5EA-6032-4A11-A9B2-6D48CB5EB082} = {4DA84F2B-1948-439B-85AB-E99E31331A9C}
1140111439
{74377D3E-E0C6-41A4-89ED-11A9C00142A9} = {166E48ED-9738-4E13-8618-0D805F6F0F65}
1140211440
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB} = {74377D3E-E0C6-41A4-89ED-11A9C00142A9}
11441+
{F057512B-55BF-4A8B-A027-A0505F8BA10C} = {4FDDC525-4E60-4CAF-83A3-261C5B43721F}
11442+
{10173568-A65E-44E5-8C6F-4AA49D0577A1} = {F057512B-55BF-4A8B-A027-A0505F8BA10C}
11443+
{97C7D2A4-87E5-4A4A-A170-D736427D5C21} = {F057512B-55BF-4A8B-A027-A0505F8BA10C}
1140311444
EndGlobalSection
1140411445
GlobalSection(ExtensibilityGlobals) = postSolution
1140511446
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}

eng/ProjectReferences.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.IIS" ProjectPath="$(RepoRoot)src\Servers\IIS\IIS\src\Microsoft.AspNetCore.Server.IIS.csproj" />
5151
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Core" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj" />
5252
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Kestrel\src\Microsoft.AspNetCore.Server.Kestrel.csproj" />
53+
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.NamedPipes\src\Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.csproj" />
5354
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Quic\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj" />
5455
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Sockets\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj" />
5556
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication.Certificate" ProjectPath="$(RepoRoot)src\Security\Authentication\Certificate\src\Microsoft.AspNetCore.Authentication.Certificate.csproj" />

eng/SharedFramework.Local.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Server.IIS" />
6161
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Server.Kestrel.Core" />
6262
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Server.Kestrel" />
63+
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes" />
6364
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" />
6465
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" />
6566
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Authentication.Cookies" />

eng/TrimmableProjects.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
<TrimmableProject Include="Microsoft.AspNetCore.Server.IIS" />
4141
<TrimmableProject Include="Microsoft.AspNetCore.Server.Kestrel.Core" />
4242
<TrimmableProject Include="Microsoft.AspNetCore.Server.Kestrel" />
43+
<TrimmableProject Include="Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes" />
4344
<TrimmableProject Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" />
4445
<TrimmableProject Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" />
4546
<TrimmableProject Include="Microsoft.AspNetCore.Authentication.Certificate" />

src/Framework/test/TestData.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ static TestData()
9090
"Microsoft.AspNetCore.Server.Kestrel",
9191
"Microsoft.AspNetCore.Server.Kestrel.Core",
9292
"Microsoft.AspNetCore.Server.Kestrel.Transport.Quic",
93+
"Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes",
9394
"Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets",
9495
"Microsoft.AspNetCore.Session",
9596
"Microsoft.AspNetCore.SignalR",
@@ -228,6 +229,7 @@ static TestData()
228229
{ "Microsoft.AspNetCore.Server.Kestrel.Core" },
229230
{ "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" },
230231
{ "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" },
232+
{ "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes" },
231233
{ "Microsoft.AspNetCore.Server.Kestrel" },
232234
{ "Microsoft.AspNetCore.Session" },
233235
{ "Microsoft.AspNetCore.SignalR.Common" },

src/Http/Http/src/BindingAddress.cs

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Http;
1111
public class BindingAddress
1212
{
1313
private const string UnixPipeHostPrefix = "unix:/";
14+
private const string NamedPipeHostPrefix = "pipe:/";
1415

1516
private BindingAddress(string host, string pathBase, int port, string scheme)
1617
{
@@ -57,6 +58,14 @@ public BindingAddress()
5758
/// </summary>
5859
public bool IsUnixPipe => Host.StartsWith(UnixPipeHostPrefix, StringComparison.Ordinal);
5960

61+
/// <summary>
62+
/// Gets a value that determines if this instance represents a named pipe.
63+
/// <para>
64+
/// Returns <see langword="true"/> if <see cref="Host"/> starts with <c>pipe:/</c> prefix.
65+
/// </para>
66+
/// </summary>
67+
public bool IsNamedPipe => Host.StartsWith(NamedPipeHostPrefix, StringComparison.Ordinal);
68+
6069
/// <summary>
6170
/// Gets the unix pipe path if this instance represents a Unix pipe.
6271
/// </summary>
@@ -73,6 +82,22 @@ public string UnixPipePath
7382
}
7483
}
7584

85+
/// <summary>
86+
/// Gets the named pipe name if this instance represents a named pipe.
87+
/// </summary>
88+
public string NamedPipeName
89+
{
90+
get
91+
{
92+
if (!IsNamedPipe)
93+
{
94+
throw new InvalidOperationException("Binding address is not a named pipe.");
95+
}
96+
97+
return GetNamedPipeName(Host);
98+
}
99+
}
100+
76101
private static string GetUnixPipePath(string host)
77102
{
78103
var unixPipeHostPrefixLength = UnixPipeHostPrefix.Length;
@@ -84,10 +109,12 @@ private static string GetUnixPipePath(string host)
84109
return host.Substring(unixPipeHostPrefixLength);
85110
}
86111

112+
private static string GetNamedPipeName(string host) => host.Substring(NamedPipeHostPrefix.Length);
113+
87114
/// <inheritdoc />
88115
public override string ToString()
89116
{
90-
if (IsUnixPipe)
117+
if (IsUnixPipe || IsNamedPipe)
91118
{
92119
return Scheme.ToLowerInvariant() + Uri.SchemeDelimiter + Host.ToLowerInvariant();
93120
}
@@ -135,15 +162,11 @@ public static BindingAddress Parse(string address)
135162
var schemeDelimiterEnd = schemeDelimiterStart + Uri.SchemeDelimiter.Length;
136163

137164
var isUnixPipe = address.IndexOf(UnixPipeHostPrefix, schemeDelimiterEnd, StringComparison.Ordinal) == schemeDelimiterEnd;
165+
var isNamedPipe = address.IndexOf(NamedPipeHostPrefix, schemeDelimiterEnd, StringComparison.Ordinal) == schemeDelimiterEnd;
138166

139167
int pathDelimiterStart;
140168
int pathDelimiterEnd;
141-
if (!isUnixPipe)
142-
{
143-
pathDelimiterStart = address.IndexOf("/", schemeDelimiterEnd, StringComparison.Ordinal);
144-
pathDelimiterEnd = pathDelimiterStart;
145-
}
146-
else
169+
if (isUnixPipe)
147170
{
148171
var unixPipeHostPrefixLength = UnixPipeHostPrefix.Length;
149172
if (OperatingSystem.IsWindows())
@@ -159,6 +182,16 @@ public static BindingAddress Parse(string address)
159182
pathDelimiterStart = address.IndexOf(":", schemeDelimiterEnd + unixPipeHostPrefixLength, StringComparison.Ordinal);
160183
pathDelimiterEnd = pathDelimiterStart + ":".Length;
161184
}
185+
else if (isNamedPipe)
186+
{
187+
pathDelimiterStart = address.IndexOf(":", schemeDelimiterEnd + NamedPipeHostPrefix.Length, StringComparison.Ordinal);
188+
pathDelimiterEnd = pathDelimiterStart + ":".Length;
189+
}
190+
else
191+
{
192+
pathDelimiterStart = address.IndexOf("/", schemeDelimiterEnd, StringComparison.Ordinal);
193+
pathDelimiterEnd = pathDelimiterStart;
194+
}
162195

163196
if (pathDelimiterStart < 0)
164197
{
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
#nullable enable
22
*REMOVED*Microsoft.AspNetCore.Http.StreamResponseBodyFeature.StreamResponseBodyFeature(System.IO.Stream! stream, Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature! priorFeature) -> void
3+
Microsoft.AspNetCore.Http.BindingAddress.IsNamedPipe.get -> bool
4+
Microsoft.AspNetCore.Http.BindingAddress.NamedPipeName.get -> string!
35
Microsoft.AspNetCore.Http.StreamResponseBodyFeature.StreamResponseBodyFeature(System.IO.Stream! stream, Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature? priorFeature) -> void
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.IO.Pipes;
5+
6+
namespace Microsoft.AspNetCore.Connections.Features;
7+
8+
/// <summary>
9+
/// Provides access to the connection's underlying <see cref="NamedPipeServerStream"/>.
10+
/// </summary>
11+
public interface IConnectionNamedPipeFeature
12+
{
13+
/// <summary>
14+
/// Gets the underlying <see cref="NamedPipeServerStream"/>.
15+
/// </summary>
16+
NamedPipeServerStream NamedPipe { get; }
17+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
using System.Net;
6+
7+
namespace Microsoft.AspNetCore.Connections;
8+
9+
/// <summary>
10+
/// Represents a Named Pipe endpoint.
11+
/// </summary>
12+
public sealed class NamedPipeEndPoint : EndPoint
13+
{
14+
internal const string LocalComputerServerName = ".";
15+
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="NamedPipeEndPoint"/> class.
18+
/// </summary>
19+
/// <param name="pipeName">The name of the pipe.</param>
20+
public NamedPipeEndPoint(string pipeName) : this(pipeName, LocalComputerServerName)
21+
{
22+
}
23+
24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="NamedPipeEndPoint"/> class.
26+
/// </summary>
27+
/// <param name="pipeName">The name of the pipe.</param>
28+
/// <param name="serverName">The name of the remote computer to connect to, or "." to specify the local computer.</param>
29+
public NamedPipeEndPoint(string pipeName, string serverName)
30+
{
31+
ServerName = serverName;
32+
PipeName = pipeName;
33+
}
34+
35+
/// <summary>
36+
/// Gets the name of the remote computer. The server name must be ".", the local computer, when creating a server.
37+
/// </summary>
38+
public string ServerName { get; }
39+
40+
/// <summary>
41+
/// Gets the name of the pipe.
42+
/// </summary>
43+
public string PipeName { get; }
44+
45+
/// <summary>
46+
/// Gets the pipe name represented by this <see cref="NamedPipeEndPoint"/> instance.
47+
/// </summary>
48+
public override string ToString()
49+
{
50+
// Based on format at https://learn.microsoft.com/windows/win32/ipc/pipe-names
51+
return $@"\\{ServerName}\pipe\{PipeName}";
52+
}
53+
54+
/// <inheritdoc/>
55+
public override bool Equals([NotNullWhen(true)] object? obj)
56+
{
57+
return obj is NamedPipeEndPoint other && other.ServerName == ServerName && other.PipeName == PipeName;
58+
}
59+
60+
/// <inheritdoc/>
61+
public override int GetHashCode()
62+
{
63+
return ServerName.GetHashCode() ^ PipeName.GetHashCode();
64+
}
65+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Connections.Features.IConnectionNamedPipeFeature
3+
Microsoft.AspNetCore.Connections.Features.IConnectionNamedPipeFeature.NamedPipe.get -> System.IO.Pipes.NamedPipeServerStream!
24
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature
35
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature.OnClosed(System.Action<object?>! callback, object? state) -> void
46
Microsoft.AspNetCore.Connections.IConnectionListenerFactorySelector
57
Microsoft.AspNetCore.Connections.IConnectionListenerFactorySelector.CanBind(System.Net.EndPoint! endpoint) -> bool
8+
Microsoft.AspNetCore.Connections.NamedPipeEndPoint
9+
Microsoft.AspNetCore.Connections.NamedPipeEndPoint.NamedPipeEndPoint(string! pipeName) -> void
10+
Microsoft.AspNetCore.Connections.NamedPipeEndPoint.NamedPipeEndPoint(string! pipeName, string! serverName) -> void
11+
Microsoft.AspNetCore.Connections.NamedPipeEndPoint.PipeName.get -> string!
12+
Microsoft.AspNetCore.Connections.NamedPipeEndPoint.ServerName.get -> string!
13+
override Microsoft.AspNetCore.Connections.NamedPipeEndPoint.Equals(object? obj) -> bool
14+
override Microsoft.AspNetCore.Connections.NamedPipeEndPoint.GetHashCode() -> int
15+
override Microsoft.AspNetCore.Connections.NamedPipeEndPoint.ToString() -> string!

0 commit comments

Comments
 (0)