Skip to content

Commit

Permalink
Initial (partially-reviewed API) System.Net.Connections. (#39524)
Browse files Browse the repository at this point in the history
  • Loading branch information
scalablecory committed Jul 28, 2020
1 parent a9009db commit 4b8c5fe
Show file tree
Hide file tree
Showing 55 changed files with 2,638 additions and 186 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Text;
using System.Threading.Tasks;
using System.Security.Authentication;
using System.IO;
using System.Net.Sockets;

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

public abstract Task<GenericLoopbackConnection> CreateConnectionAsync(Socket socket, Stream stream, GenericLoopbackOptions options = null);

public abstract Version Version { get; }

// Common helper methods
Expand Down Expand Up @@ -58,6 +62,8 @@ public abstract class GenericLoopbackConnection : IDisposable
{
public abstract void Dispose();

public abstract Task InitializeConnectionAsync();

/// <summary>Read request Headers and optionally request body as well.</summary>
public abstract Task<HttpRequestData> ReadRequestDataAsync(bool readBody = true);
/// <summary>Read complete request body if not done by ReadRequestData.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Net.Http.Functional.Tests;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -24,28 +25,31 @@ public class Http2LoopbackConnection : GenericLoopbackConnection
private readonly TimeSpan _timeout;
private int _lastStreamId;

private readonly byte[] _prefix;
private readonly byte[] _prefix = new byte[24];
public string PrefixString => Encoding.UTF8.GetString(_prefix, 0, _prefix.Length);
public bool IsInvalid => _connectionSocket == null;
public Stream Stream => _connectionStream;
public Task<bool> SettingAckWaiter => _ignoredSettingsAckPromise?.Task;

public Http2LoopbackConnection(Socket socket, Http2Options httpOptions)
: this(socket, httpOptions, Http2LoopbackServer.Timeout)
private Http2LoopbackConnection(Socket socket, Stream stream, TimeSpan timeout)
{
_connectionSocket = socket;
_connectionStream = stream;
_timeout = timeout;
}

public Http2LoopbackConnection(Socket socket, Http2Options httpOptions, TimeSpan timeout)
public static Task<Http2LoopbackConnection> CreateAsync(Socket socket, Stream stream, Http2Options httpOptions)
{
_connectionSocket = socket;
_connectionStream = new NetworkStream(_connectionSocket, true);
_timeout = timeout;
return CreateAsync(socket, stream, httpOptions, Http2LoopbackServer.Timeout);
}

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

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

options.ClientCertificateRequired = httpOptions.ClientCertificateRequired;

sslStream.AuthenticateAsServerAsync(options, CancellationToken.None).Wait();
await sslStream.AuthenticateAsServerAsync(options, CancellationToken.None).ConfigureAwait(false);
#else
sslStream.AuthenticateAsServerAsync(cert, httpOptions.ClientCertificateRequired, httpOptions.SslProtocols, checkCertificateRevocation: false).Wait();
await sslStream.AuthenticateAsServerAsync(cert, httpOptions.ClientCertificateRequired, httpOptions.SslProtocols, checkCertificateRevocation: false).ConfigureAwait(false);
#endif
}

_connectionStream = sslStream;
stream = sslStream;
}

_prefix = new byte[24];
if (!FillBufferAsync(_prefix).Result)
var con = new Http2LoopbackConnection(socket, stream, timeout);
await con.ReadPrefixAsync().ConfigureAwait(false);

return con;
}

private async Task ReadPrefixAsync()
{
if (!await FillBufferAsync(_prefix))
{
throw new Exception("Connection stream closed while attempting to read connection preface.");
}
else if (Text.Encoding.ASCII.GetString(_prefix).Contains("HTTP/1.1"))

if (Text.Encoding.ASCII.GetString(_prefix).Contains("HTTP/1.1"))
{
throw new Exception("HTTP 1.1 request received.");
}
Expand Down Expand Up @@ -275,7 +287,7 @@ public async Task WaitForClientDisconnectAsync(bool ignoreUnexpectedFrames = fal

public void ShutdownSend()
{
_connectionSocket.Shutdown(SocketShutdown.Send);
_connectionSocket?.Shutdown(SocketShutdown.Send);
}

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

public override Task InitializeConnectionAsync()
{
return ReadAndSendSettingsAsync(ackTimeout: null);
}

public async Task<SettingsFrame> ReadAndSendSettingsAsync(TimeSpan? ackTimeout, params SettingsEntry[] settingsEntries)
{
// Receive the initial client settings frame.
Frame receivedFrame = await ReadFrameAsync(_timeout).ConfigureAwait(false);
Assert.Equal(FrameType.Settings, receivedFrame.Type);
Assert.Equal(FrameFlags.None, receivedFrame.Flags);
Assert.Equal(0, receivedFrame.StreamId);

var clientSettingsFrame = (SettingsFrame)receivedFrame;

// Receive the initial client window update frame.
receivedFrame = await ReadFrameAsync(_timeout).ConfigureAwait(false);
Assert.Equal(FrameType.WindowUpdate, receivedFrame.Type);
Assert.Equal(FrameFlags.None, receivedFrame.Flags);
Assert.Equal(0, receivedFrame.StreamId);

// Send the initial server settings frame.
SettingsFrame settingsFrame = new SettingsFrame(settingsEntries);
await WriteFrameAsync(settingsFrame).ConfigureAwait(false);

// Send the client settings frame ACK.
Frame settingsAck = new Frame(0, FrameType.Settings, FrameFlags.Ack, 0);
await WriteFrameAsync(settingsAck).ConfigureAwait(false);

// The client will send us a SETTINGS ACK eventually, but not necessarily right away.
await ExpectSettingsAckAsync((int?)ackTimeout?.TotalMilliseconds ?? 5000);

return clientSettingsFrame;
}

public async Task SendGoAway(int lastStreamId, ProtocolErrors errorCode = ProtocolErrors.NO_ERROR)
{
GoAwayFrame frame = new GoAwayFrame(lastStreamId, (int)errorCode, new byte[] { }, 0);
Expand Down
49 changes: 18 additions & 31 deletions src/libraries/Common/tests/System/Net/Http/Http2LoopbackServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ public async Task<Http2LoopbackConnection> AcceptConnectionAsync(TimeSpan? timeo

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

Http2LoopbackConnection connection = timeout != null ? new Http2LoopbackConnection(connectionSocket, _options, timeout.Value) : new Http2LoopbackConnection(connectionSocket, _options);
var stream = new NetworkStream(connectionSocket, ownsSocket: true);
Http2LoopbackConnection connection =
timeout != null ? await Http2LoopbackConnection.CreateAsync(connectionSocket, stream, _options, timeout.Value).ConfigureAwait(false) :
await Http2LoopbackConnection.CreateAsync(connectionSocket, stream, _options).ConfigureAwait(false);
_connections.Add(connection);

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

public Task<Http2LoopbackConnection> EstablishConnectionAsync(params SettingsEntry[] settingsEntries)
{
return EstablishConnectionAsync(null, null, settingsEntries);
return EstablishConnectionAsync(timeout: null, ackTimeout: null, settingsEntries);
}

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

public Task<(Http2LoopbackConnection, SettingsFrame)> EstablishConnectionGetSettingsAsync(params SettingsEntry[] settingsEntries)
{
return EstablishConnectionGetSettingsAsync(null, null, settingsEntries);
return EstablishConnectionGetSettingsAsync(timeout: null, ackTimeout: null, settingsEntries);
}

public async Task<(Http2LoopbackConnection, SettingsFrame)> EstablishConnectionGetSettingsAsync(TimeSpan? timeout, TimeSpan? ackTimeout, params SettingsEntry[] settingsEntries)
{
Http2LoopbackConnection connection = await AcceptConnectionAsync(timeout).ConfigureAwait(false);

// Receive the initial client settings frame.
Frame receivedFrame = await connection.ReadFrameAsync(Timeout).ConfigureAwait(false);
Assert.Equal(FrameType.Settings, receivedFrame.Type);
Assert.Equal(FrameFlags.None, receivedFrame.Flags);
Assert.Equal(0, receivedFrame.StreamId);

var clientSettingsFrame = (SettingsFrame)receivedFrame;

// Receive the initial client window update frame.
receivedFrame = await connection.ReadFrameAsync(Timeout).ConfigureAwait(false);
Assert.Equal(FrameType.WindowUpdate, receivedFrame.Type);
Assert.Equal(FrameFlags.None, receivedFrame.Flags);
Assert.Equal(0, receivedFrame.StreamId);

// Send the initial server settings frame.
SettingsFrame settingsFrame = new SettingsFrame(settingsEntries);
await connection.WriteFrameAsync(settingsFrame).ConfigureAwait(false);

// Send the client settings frame ACK.
Frame settingsAck = new Frame(0, FrameType.Settings, FrameFlags.Ack, 0);
await connection.WriteFrameAsync(settingsAck).ConfigureAwait(false);

// The client will send us a SETTINGS ACK eventually, but not necessarily right away.
await connection.ExpectSettingsAckAsync((int) (ackTimeout?.TotalMilliseconds ?? 5000));
SettingsFrame clientSettingsFrame = await connection.ReadAndSendSettingsAsync(ackTimeout, settingsEntries).ConfigureAwait(false);

return (connection, clientSettingsFrame);
}
Expand Down Expand Up @@ -220,7 +199,6 @@ public class Http2Options : GenericLoopbackOptions

public Http2Options()
{
UseSsl = PlatformDetection.SupportsAlpn && !Capability.Http2ForceUnencryptedLoopback();
SslProtocols = SslProtocols.Tls12;
}
}
Expand All @@ -238,6 +216,16 @@ public static async Task CreateServerAsync(Func<Http2LoopbackServer, Uri, Task>
}

public override GenericLoopbackServer CreateServer(GenericLoopbackOptions options = null)
{
return Http2LoopbackServer.CreateServer(CreateOptions(options));
}

public override async Task<GenericLoopbackConnection> CreateConnectionAsync(Socket socket, Stream stream, GenericLoopbackOptions options = null)
{
return await Http2LoopbackConnection.CreateAsync(socket, stream, CreateOptions(options)).ConfigureAwait(false);
}

private static Http2Options CreateOptions(GenericLoopbackOptions options)
{
Http2Options http2Options = new Http2Options();
if (options != null)
Expand All @@ -246,8 +234,7 @@ public override GenericLoopbackServer CreateServer(GenericLoopbackOptions option
http2Options.UseSsl = options.UseSsl;
http2Options.SslProtocols = options.SslProtocols;
}

return Http2LoopbackServer.CreateServer(http2Options);
return http2Options;
}

public override async Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Task> funcAsync, int millisecondsTimeout = 60_000, GenericLoopbackOptions options = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using System.Net.Http.Functional.Tests;

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

public override Task InitializeConnectionAsync()
{
throw new NotImplementedException();
}

public async Task<Http3LoopbackStream> AcceptStreamAsync()
{
QuicStream quicStream = await _connection.AcceptStreamAsync().ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.Quic;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

Expand Down Expand Up @@ -80,5 +82,12 @@ public override async Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Ta
using GenericLoopbackServer server = CreateServer(options);
await funcAsync(server, server.Address).TimeoutAfter(millisecondsTimeout).ConfigureAwait(false);
}

public override Task<GenericLoopbackConnection> CreateConnectionAsync(Socket socket, Stream stream, GenericLoopbackOptions options = null)
{
// TODO: make a new overload that takes a MultiplexedConnection.
// This method is always unacceptable to call for HTTP/3.
throw new NotImplementedException("HTTP/3 does not operate over a Socket.");
}
}
}
14 changes: 12 additions & 2 deletions src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -594,13 +594,18 @@ public override void Dispose()
// This seems to help avoid connection reset issues caused by buffered data
// that has not been sent/acked when the graceful shutdown timeout expires.
// This may throw if the socket was already closed, so eat any exception.
_socket.Shutdown(SocketShutdown.Send);
_socket?.Shutdown(SocketShutdown.Send);
}
catch (Exception) { }

_writer.Dispose();
_stream.Dispose();
_socket.Dispose();
_socket?.Dispose();
}

public override Task InitializeConnectionAsync()
{
return Task.CompletedTask;
}

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

public override Task<GenericLoopbackConnection> CreateConnectionAsync(Socket socket, Stream stream, GenericLoopbackOptions options = null)
{
return Task.FromResult<GenericLoopbackConnection>(new LoopbackServer.Connection(socket, stream));
}

private static LoopbackServer.Options CreateOptions(GenericLoopbackOptions options)
{
LoopbackServer.Options newOptions = new LoopbackServer.Options();
Expand Down
Loading

0 comments on commit 4b8c5fe

Please sign in to comment.