Skip to content

Commit 4b8c5fe

Browse files
authored
Initial (partially-reviewed API) System.Net.Connections. (#39524)
1 parent a9009db commit 4b8c5fe

File tree

55 files changed

+2638
-186
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2638
-186
lines changed

src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using System.Text;
77
using System.Threading.Tasks;
88
using System.Security.Authentication;
9+
using System.IO;
10+
using System.Net.Sockets;
911

1012
namespace System.Net.Test.Common
1113
{
@@ -17,6 +19,8 @@ public abstract class LoopbackServerFactory
1719
public abstract GenericLoopbackServer CreateServer(GenericLoopbackOptions options = null);
1820
public abstract Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Task> funcAsync, int millisecondsTimeout = 60_000, GenericLoopbackOptions options = null);
1921

22+
public abstract Task<GenericLoopbackConnection> CreateConnectionAsync(Socket socket, Stream stream, GenericLoopbackOptions options = null);
23+
2024
public abstract Version Version { get; }
2125

2226
// Common helper methods
@@ -58,6 +62,8 @@ public abstract class GenericLoopbackConnection : IDisposable
5862
{
5963
public abstract void Dispose();
6064

65+
public abstract Task InitializeConnectionAsync();
66+
6167
/// <summary>Read request Headers and optionally request body as well.</summary>
6268
public abstract Task<HttpRequestData> ReadRequestDataAsync(bool readBody = true);
6369
/// <summary>Read complete request body if not done by ReadRequestData.</summary>

src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Net.Http.Functional.Tests;
88
using System.Net.Security;
99
using System.Net.Sockets;
10+
using System.Security.Cryptography.X509Certificates;
1011
using System.Text;
1112
using System.Threading;
1213
using System.Threading.Tasks;
@@ -24,28 +25,31 @@ public class Http2LoopbackConnection : GenericLoopbackConnection
2425
private readonly TimeSpan _timeout;
2526
private int _lastStreamId;
2627

27-
private readonly byte[] _prefix;
28+
private readonly byte[] _prefix = new byte[24];
2829
public string PrefixString => Encoding.UTF8.GetString(_prefix, 0, _prefix.Length);
2930
public bool IsInvalid => _connectionSocket == null;
3031
public Stream Stream => _connectionStream;
3132
public Task<bool> SettingAckWaiter => _ignoredSettingsAckPromise?.Task;
3233

33-
public Http2LoopbackConnection(Socket socket, Http2Options httpOptions)
34-
: this(socket, httpOptions, Http2LoopbackServer.Timeout)
34+
private Http2LoopbackConnection(Socket socket, Stream stream, TimeSpan timeout)
3535
{
36+
_connectionSocket = socket;
37+
_connectionStream = stream;
38+
_timeout = timeout;
3639
}
3740

38-
public Http2LoopbackConnection(Socket socket, Http2Options httpOptions, TimeSpan timeout)
41+
public static Task<Http2LoopbackConnection> CreateAsync(Socket socket, Stream stream, Http2Options httpOptions)
3942
{
40-
_connectionSocket = socket;
41-
_connectionStream = new NetworkStream(_connectionSocket, true);
42-
_timeout = timeout;
43+
return CreateAsync(socket, stream, httpOptions, Http2LoopbackServer.Timeout);
44+
}
4345

46+
public static async Task<Http2LoopbackConnection> CreateAsync(Socket socket, Stream stream, Http2Options httpOptions, TimeSpan timeout)
47+
{
4448
if (httpOptions.UseSsl)
4549
{
46-
var sslStream = new SslStream(_connectionStream, false, delegate { return true; });
50+
var sslStream = new SslStream(stream, false, delegate { return true; });
4751

48-
using (var cert = Configuration.Certificates.GetServerCertificate())
52+
using (X509Certificate2 cert = Configuration.Certificates.GetServerCertificate())
4953
{
5054
#if !NETFRAMEWORK
5155
SslServerAuthenticationOptions options = new SslServerAuthenticationOptions();
@@ -61,21 +65,29 @@ public Http2LoopbackConnection(Socket socket, Http2Options httpOptions, TimeSpan
6165

6266
options.ClientCertificateRequired = httpOptions.ClientCertificateRequired;
6367

64-
sslStream.AuthenticateAsServerAsync(options, CancellationToken.None).Wait();
68+
await sslStream.AuthenticateAsServerAsync(options, CancellationToken.None).ConfigureAwait(false);
6569
#else
66-
sslStream.AuthenticateAsServerAsync(cert, httpOptions.ClientCertificateRequired, httpOptions.SslProtocols, checkCertificateRevocation: false).Wait();
70+
await sslStream.AuthenticateAsServerAsync(cert, httpOptions.ClientCertificateRequired, httpOptions.SslProtocols, checkCertificateRevocation: false).ConfigureAwait(false);
6771
#endif
6872
}
6973

70-
_connectionStream = sslStream;
74+
stream = sslStream;
7175
}
7276

73-
_prefix = new byte[24];
74-
if (!FillBufferAsync(_prefix).Result)
77+
var con = new Http2LoopbackConnection(socket, stream, timeout);
78+
await con.ReadPrefixAsync().ConfigureAwait(false);
79+
80+
return con;
81+
}
82+
83+
private async Task ReadPrefixAsync()
84+
{
85+
if (!await FillBufferAsync(_prefix))
7586
{
7687
throw new Exception("Connection stream closed while attempting to read connection preface.");
7788
}
78-
else if (Text.Encoding.ASCII.GetString(_prefix).Contains("HTTP/1.1"))
89+
90+
if (Text.Encoding.ASCII.GetString(_prefix).Contains("HTTP/1.1"))
7991
{
8092
throw new Exception("HTTP 1.1 request received.");
8193
}
@@ -275,7 +287,7 @@ public async Task WaitForClientDisconnectAsync(bool ignoreUnexpectedFrames = fal
275287

276288
public void ShutdownSend()
277289
{
278-
_connectionSocket.Shutdown(SocketShutdown.Send);
290+
_connectionSocket?.Shutdown(SocketShutdown.Send);
279291
}
280292

281293
// This will cause a server-initiated shutdown of the connection.
@@ -563,6 +575,41 @@ public async Task<byte[]> ReadBodyAsync(bool expectEndOfStream = false)
563575
return (streamId, requestData);
564576
}
565577

578+
public override Task InitializeConnectionAsync()
579+
{
580+
return ReadAndSendSettingsAsync(ackTimeout: null);
581+
}
582+
583+
public async Task<SettingsFrame> ReadAndSendSettingsAsync(TimeSpan? ackTimeout, params SettingsEntry[] settingsEntries)
584+
{
585+
// Receive the initial client settings frame.
586+
Frame receivedFrame = await ReadFrameAsync(_timeout).ConfigureAwait(false);
587+
Assert.Equal(FrameType.Settings, receivedFrame.Type);
588+
Assert.Equal(FrameFlags.None, receivedFrame.Flags);
589+
Assert.Equal(0, receivedFrame.StreamId);
590+
591+
var clientSettingsFrame = (SettingsFrame)receivedFrame;
592+
593+
// Receive the initial client window update frame.
594+
receivedFrame = await ReadFrameAsync(_timeout).ConfigureAwait(false);
595+
Assert.Equal(FrameType.WindowUpdate, receivedFrame.Type);
596+
Assert.Equal(FrameFlags.None, receivedFrame.Flags);
597+
Assert.Equal(0, receivedFrame.StreamId);
598+
599+
// Send the initial server settings frame.
600+
SettingsFrame settingsFrame = new SettingsFrame(settingsEntries);
601+
await WriteFrameAsync(settingsFrame).ConfigureAwait(false);
602+
603+
// Send the client settings frame ACK.
604+
Frame settingsAck = new Frame(0, FrameType.Settings, FrameFlags.Ack, 0);
605+
await WriteFrameAsync(settingsAck).ConfigureAwait(false);
606+
607+
// The client will send us a SETTINGS ACK eventually, but not necessarily right away.
608+
await ExpectSettingsAckAsync((int?)ackTimeout?.TotalMilliseconds ?? 5000);
609+
610+
return clientSettingsFrame;
611+
}
612+
566613
public async Task SendGoAway(int lastStreamId, ProtocolErrors errorCode = ProtocolErrors.NO_ERROR)
567614
{
568615
GoAwayFrame frame = new GoAwayFrame(lastStreamId, (int)errorCode, new byte[] { }, 0);

src/libraries/Common/tests/System/Net/Http/Http2LoopbackServer.cs

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ public async Task<Http2LoopbackConnection> AcceptConnectionAsync(TimeSpan? timeo
9090

9191
Socket connectionSocket = await _listenSocket.AcceptAsync().ConfigureAwait(false);
9292

93-
Http2LoopbackConnection connection = timeout != null ? new Http2LoopbackConnection(connectionSocket, _options, timeout.Value) : new Http2LoopbackConnection(connectionSocket, _options);
93+
var stream = new NetworkStream(connectionSocket, ownsSocket: true);
94+
Http2LoopbackConnection connection =
95+
timeout != null ? await Http2LoopbackConnection.CreateAsync(connectionSocket, stream, _options, timeout.Value).ConfigureAwait(false) :
96+
await Http2LoopbackConnection.CreateAsync(connectionSocket, stream, _options).ConfigureAwait(false);
9497
_connections.Add(connection);
9598

9699
return connection;
@@ -103,7 +106,7 @@ public override async Task<GenericLoopbackConnection> EstablishGenericConnection
103106

104107
public Task<Http2LoopbackConnection> EstablishConnectionAsync(params SettingsEntry[] settingsEntries)
105108
{
106-
return EstablishConnectionAsync(null, null, settingsEntries);
109+
return EstablishConnectionAsync(timeout: null, ackTimeout: null, settingsEntries);
107110
}
108111

109112
public async Task<Http2LoopbackConnection> EstablishConnectionAsync(TimeSpan? timeout, TimeSpan? ackTimeout, params SettingsEntry[] settingsEntries)
@@ -114,37 +117,13 @@ public async Task<Http2LoopbackConnection> EstablishConnectionAsync(TimeSpan? ti
114117

115118
public Task<(Http2LoopbackConnection, SettingsFrame)> EstablishConnectionGetSettingsAsync(params SettingsEntry[] settingsEntries)
116119
{
117-
return EstablishConnectionGetSettingsAsync(null, null, settingsEntries);
120+
return EstablishConnectionGetSettingsAsync(timeout: null, ackTimeout: null, settingsEntries);
118121
}
119122

120123
public async Task<(Http2LoopbackConnection, SettingsFrame)> EstablishConnectionGetSettingsAsync(TimeSpan? timeout, TimeSpan? ackTimeout, params SettingsEntry[] settingsEntries)
121124
{
122125
Http2LoopbackConnection connection = await AcceptConnectionAsync(timeout).ConfigureAwait(false);
123-
124-
// Receive the initial client settings frame.
125-
Frame receivedFrame = await connection.ReadFrameAsync(Timeout).ConfigureAwait(false);
126-
Assert.Equal(FrameType.Settings, receivedFrame.Type);
127-
Assert.Equal(FrameFlags.None, receivedFrame.Flags);
128-
Assert.Equal(0, receivedFrame.StreamId);
129-
130-
var clientSettingsFrame = (SettingsFrame)receivedFrame;
131-
132-
// Receive the initial client window update frame.
133-
receivedFrame = await connection.ReadFrameAsync(Timeout).ConfigureAwait(false);
134-
Assert.Equal(FrameType.WindowUpdate, receivedFrame.Type);
135-
Assert.Equal(FrameFlags.None, receivedFrame.Flags);
136-
Assert.Equal(0, receivedFrame.StreamId);
137-
138-
// Send the initial server settings frame.
139-
SettingsFrame settingsFrame = new SettingsFrame(settingsEntries);
140-
await connection.WriteFrameAsync(settingsFrame).ConfigureAwait(false);
141-
142-
// Send the client settings frame ACK.
143-
Frame settingsAck = new Frame(0, FrameType.Settings, FrameFlags.Ack, 0);
144-
await connection.WriteFrameAsync(settingsAck).ConfigureAwait(false);
145-
146-
// The client will send us a SETTINGS ACK eventually, but not necessarily right away.
147-
await connection.ExpectSettingsAckAsync((int) (ackTimeout?.TotalMilliseconds ?? 5000));
126+
SettingsFrame clientSettingsFrame = await connection.ReadAndSendSettingsAsync(ackTimeout, settingsEntries).ConfigureAwait(false);
148127

149128
return (connection, clientSettingsFrame);
150129
}
@@ -220,7 +199,6 @@ public class Http2Options : GenericLoopbackOptions
220199

221200
public Http2Options()
222201
{
223-
UseSsl = PlatformDetection.SupportsAlpn && !Capability.Http2ForceUnencryptedLoopback();
224202
SslProtocols = SslProtocols.Tls12;
225203
}
226204
}
@@ -238,6 +216,16 @@ public static async Task CreateServerAsync(Func<Http2LoopbackServer, Uri, Task>
238216
}
239217

240218
public override GenericLoopbackServer CreateServer(GenericLoopbackOptions options = null)
219+
{
220+
return Http2LoopbackServer.CreateServer(CreateOptions(options));
221+
}
222+
223+
public override async Task<GenericLoopbackConnection> CreateConnectionAsync(Socket socket, Stream stream, GenericLoopbackOptions options = null)
224+
{
225+
return await Http2LoopbackConnection.CreateAsync(socket, stream, CreateOptions(options)).ConfigureAwait(false);
226+
}
227+
228+
private static Http2Options CreateOptions(GenericLoopbackOptions options)
241229
{
242230
Http2Options http2Options = new Http2Options();
243231
if (options != null)
@@ -246,8 +234,7 @@ public override GenericLoopbackServer CreateServer(GenericLoopbackOptions option
246234
http2Options.UseSsl = options.UseSsl;
247235
http2Options.SslProtocols = options.SslProtocols;
248236
}
249-
250-
return Http2LoopbackServer.CreateServer(http2Options);
237+
return http2Options;
251238
}
252239

253240
public override async Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Task> funcAsync, int millisecondsTimeout = 60_000, GenericLoopbackOptions options = null)

src/libraries/Common/tests/System/Net/Http/Http3LoopbackConnection.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Text;
99
using System.Threading.Tasks;
1010
using System.Linq;
11+
using System.Net.Http.Functional.Tests;
1112

1213
namespace System.Net.Test.Common
1314
{
@@ -84,6 +85,11 @@ public Http3LoopbackStream GetOpenRequest(int requestId = 0)
8485
return requestId == 0 ? _currentStream : _openStreams[requestId - 1];
8586
}
8687

88+
public override Task InitializeConnectionAsync()
89+
{
90+
throw new NotImplementedException();
91+
}
92+
8793
public async Task<Http3LoopbackStream> AcceptStreamAsync()
8894
{
8995
QuicStream quicStream = await _connection.AcceptStreamAsync().ConfigureAwait(false);

src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
using System.Collections.Generic;
55
using System.Diagnostics;
6+
using System.IO;
67
using System.Net.Quic;
78
using System.Net.Security;
9+
using System.Net.Sockets;
810
using System.Security.Cryptography.X509Certificates;
911
using System.Threading.Tasks;
1012

@@ -80,5 +82,12 @@ public override async Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Ta
8082
using GenericLoopbackServer server = CreateServer(options);
8183
await funcAsync(server, server.Address).TimeoutAfter(millisecondsTimeout).ConfigureAwait(false);
8284
}
85+
86+
public override Task<GenericLoopbackConnection> CreateConnectionAsync(Socket socket, Stream stream, GenericLoopbackOptions options = null)
87+
{
88+
// TODO: make a new overload that takes a MultiplexedConnection.
89+
// This method is always unacceptable to call for HTTP/3.
90+
throw new NotImplementedException("HTTP/3 does not operate over a Socket.");
91+
}
8392
}
8493
}

src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,13 +594,18 @@ public override void Dispose()
594594
// This seems to help avoid connection reset issues caused by buffered data
595595
// that has not been sent/acked when the graceful shutdown timeout expires.
596596
// This may throw if the socket was already closed, so eat any exception.
597-
_socket.Shutdown(SocketShutdown.Send);
597+
_socket?.Shutdown(SocketShutdown.Send);
598598
}
599599
catch (Exception) { }
600600

601601
_writer.Dispose();
602602
_stream.Dispose();
603-
_socket.Dispose();
603+
_socket?.Dispose();
604+
}
605+
606+
public override Task InitializeConnectionAsync()
607+
{
608+
return Task.CompletedTask;
604609
}
605610

606611
public async Task<List<string>> ReadRequestHeaderAsync()
@@ -896,6 +901,11 @@ public override Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Task> fu
896901
return LoopbackServer.CreateServerAsync((server, uri) => funcAsync(server, uri), options: CreateOptions(options));
897902
}
898903

904+
public override Task<GenericLoopbackConnection> CreateConnectionAsync(Socket socket, Stream stream, GenericLoopbackOptions options = null)
905+
{
906+
return Task.FromResult<GenericLoopbackConnection>(new LoopbackServer.Connection(socket, stream));
907+
}
908+
899909
private static LoopbackServer.Options CreateOptions(GenericLoopbackOptions options)
900910
{
901911
LoopbackServer.Options newOptions = new LoopbackServer.Options();

0 commit comments

Comments
 (0)