Skip to content

Commit 469faaf

Browse files
authored
Added IConnectionSocketFeature (#31588)
- Removed the nullable from the Socket property as approved in API review - Make the IConnectionSocketFeature an additional feature instead of one implemented on the TransportConnection. - Added a test to verify it works
1 parent da8ac73 commit 469faaf

File tree

7 files changed

+133
-4
lines changed

7 files changed

+133
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Net.Sockets;
5+
6+
namespace Microsoft.AspNetCore.Connections.Features
7+
{
8+
/// <summary>
9+
/// Provides access to the connection's underlying <see cref="Socket"/>.
10+
/// </summary>
11+
public interface IConnectionSocketFeature
12+
{
13+
/// <summary>
14+
/// Gets the underlying <see cref="Socket"/>.
15+
/// </summary>
16+
Socket Socket { get; }
17+
}
18+
}

src/Servers/Connections.Abstractions/src/PublicAPI.Unshipped.txt

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#nullable enable
22
*REMOVED*Microsoft.AspNetCore.Connections.IConnectionListener.AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext!>
3+
Microsoft.AspNetCore.Connections.Features.IConnectionSocketFeature
4+
Microsoft.AspNetCore.Connections.Features.IConnectionSocketFeature.Socket.get -> System.Net.Sockets.Socket!
35
Microsoft.AspNetCore.Connections.IConnectionListener.AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext?>
46
Microsoft.AspNetCore.Connections.Experimental.IMultiplexedConnectionBuilder
57
Microsoft.AspNetCore.Connections.Experimental.IMultiplexedConnectionBuilder.ApplicationServices.get -> System.IServiceProvider!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Net.Sockets;
5+
using Microsoft.AspNetCore.Connections.Features;
6+
7+
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
8+
{
9+
internal sealed partial class SocketConnection : IConnectionSocketFeature
10+
{
11+
public Socket Socket => _socket;
12+
13+
private void InitiaizeFeatures()
14+
{
15+
_currentIConnectionSocketFeature = this;
16+
}
17+
}
18+
}

src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
1515
{
16-
internal sealed class SocketConnection : TransportConnection
16+
internal sealed partial class SocketConnection : TransportConnection
1717
{
1818
private static readonly int MinAllocBufferSize = SlabMemoryPool.BlockSize / 2;
1919

@@ -70,6 +70,8 @@ internal SocketConnection(Socket socket,
7070
// Set the transport and connection id
7171
Transport = _originalTransport = pair.Transport;
7272
Application = pair.Application;
73+
74+
InitiaizeFeatures();
7375
}
7476

7577
public PipeWriter Input => Application.Output;

src/Servers/Kestrel/shared/TransportConnection.Generated.cs

+24
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ internal partial class TransportConnection : IFeatureCollection,
2727
internal protected IMemoryPoolFeature? _currentIMemoryPoolFeature;
2828
internal protected IConnectionLifetimeFeature? _currentIConnectionLifetimeFeature;
2929

30+
// Other reserved feature slots
31+
internal protected IConnectionSocketFeature? _currentIConnectionSocketFeature;
32+
3033
private int _featureRevision;
3134

3235
private List<KeyValuePair<Type, object>>? MaybeExtra;
@@ -39,6 +42,7 @@ private void FastReset()
3942
_currentIMemoryPoolFeature = this;
4043
_currentIConnectionLifetimeFeature = this;
4144

45+
_currentIConnectionSocketFeature = null;
4246
}
4347

4448
// Internal for testing
@@ -130,6 +134,10 @@ private void ExtraFeatureSet(Type key, object? value)
130134
{
131135
feature = _currentIConnectionLifetimeFeature;
132136
}
137+
else if (key == typeof(IConnectionSocketFeature))
138+
{
139+
feature = _currentIConnectionSocketFeature;
140+
}
133141
else if (MaybeExtra != null)
134142
{
135143
feature = ExtraFeatureGet(key);
@@ -162,6 +170,10 @@ private void ExtraFeatureSet(Type key, object? value)
162170
{
163171
_currentIConnectionLifetimeFeature = (IConnectionLifetimeFeature?)value;
164172
}
173+
else if (key == typeof(IConnectionSocketFeature))
174+
{
175+
_currentIConnectionSocketFeature = (IConnectionSocketFeature?)value;
176+
}
165177
else
166178
{
167179
ExtraFeatureSet(key, value);
@@ -196,6 +208,10 @@ private void ExtraFeatureSet(Type key, object? value)
196208
{
197209
feature = Unsafe.As<IConnectionLifetimeFeature?, TFeature?>(ref _currentIConnectionLifetimeFeature);
198210
}
211+
else if (typeof(TFeature) == typeof(IConnectionSocketFeature))
212+
{
213+
feature = Unsafe.As<IConnectionSocketFeature?, TFeature?>(ref _currentIConnectionSocketFeature);
214+
}
199215
else if (MaybeExtra != null)
200216
{
201217
feature = (TFeature?)(ExtraFeatureGet(typeof(TFeature)));
@@ -231,6 +247,10 @@ private void ExtraFeatureSet(Type key, object? value)
231247
{
232248
_currentIConnectionLifetimeFeature = Unsafe.As<TFeature?, IConnectionLifetimeFeature?>(ref feature);
233249
}
250+
else if (typeof(TFeature) == typeof(IConnectionSocketFeature))
251+
{
252+
_currentIConnectionSocketFeature = Unsafe.As<TFeature?, IConnectionSocketFeature?>(ref feature);
253+
}
234254
else
235255
{
236256
ExtraFeatureSet(typeof(TFeature), feature);
@@ -259,6 +279,10 @@ private IEnumerable<KeyValuePair<Type, object>> FastEnumerable()
259279
{
260280
yield return new KeyValuePair<Type, object>(typeof(IConnectionLifetimeFeature), _currentIConnectionLifetimeFeature);
261281
}
282+
if (_currentIConnectionSocketFeature != null)
283+
{
284+
yield return new KeyValuePair<Type, object>(typeof(IConnectionSocketFeature), _currentIConnectionSocketFeature);
285+
}
262286

263287
if (MaybeExtra != null)
264288
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System.Net;
2+
using System.Net.Http;
3+
using System.Net.Sockets;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Connections.Features;
7+
using Microsoft.AspNetCore.Hosting;
8+
using Microsoft.AspNetCore.Server.Kestrel.FunctionalTests;
9+
using Microsoft.AspNetCore.Testing;
10+
using Microsoft.Extensions.Hosting;
11+
using Xunit;
12+
13+
namespace Sockets.FunctionalTests
14+
{
15+
public class SocketTranspotTests : LoggedTestBase
16+
{
17+
[Fact]
18+
public async Task SocketTransportExposesSocketsFeature()
19+
{
20+
var builder = TransportSelector.GetHostBuilder()
21+
.ConfigureWebHost(webHostBuilder =>
22+
{
23+
webHostBuilder
24+
.UseKestrel()
25+
.UseUrls("http://127.0.0.1:0")
26+
.Configure(app =>
27+
{
28+
app.Run(context =>
29+
{
30+
var socket = context.Features.Get<IConnectionSocketFeature>().Socket;
31+
Assert.NotNull(socket);
32+
Assert.Equal(ProtocolType.Tcp, socket.ProtocolType);
33+
var ip = (IPEndPoint)socket.RemoteEndPoint;
34+
Assert.Equal(ip.Address, context.Connection.RemoteIpAddress);
35+
Assert.Equal(ip.Port, context.Connection.RemotePort);
36+
37+
return Task.CompletedTask;
38+
});
39+
});
40+
})
41+
.ConfigureServices(AddTestLogging);
42+
43+
using var host = builder.Build();
44+
using var client = new HttpClient();
45+
46+
await host.StartAsync();
47+
48+
var response = await client.GetAsync($"http://127.0.0.1:{host.GetPort()}/");
49+
response.EnsureSuccessStatusCode();
50+
51+
await host.StopAsync();
52+
}
53+
}
54+
}

src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs

+14-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,18 @@ public static string GenerateFile()
1111
{
1212
// NOTE: This list MUST always match the set of feature interfaces implemented by TransportConnection.
1313
// See also: shared/TransportConnection.FeatureCollection.cs
14-
var features = new[]
14+
15+
var allFeatures = new[]
16+
{
17+
"IConnectionIdFeature",
18+
"IConnectionTransportFeature",
19+
"IConnectionItemsFeature",
20+
"IMemoryPoolFeature",
21+
"IConnectionLifetimeFeature",
22+
"IConnectionSocketFeature"
23+
};
24+
25+
var implementedFeatures = new[]
1526
{
1627
"IConnectionIdFeature",
1728
"IConnectionTransportFeature",
@@ -27,8 +38,8 @@ public static string GenerateFile()
2738
return FeatureCollectionGenerator.GenerateFile(
2839
namespaceName: "Microsoft.AspNetCore.Connections",
2940
className: "TransportConnection",
30-
allFeatures: features,
31-
implementedFeatures: features,
41+
allFeatures: allFeatures,
42+
implementedFeatures: implementedFeatures,
3243
extraUsings: usings,
3344
fallbackFeatures: null);
3445
}

0 commit comments

Comments
 (0)