Skip to content

Commit

Permalink
[WIP][wasm][testing] remote loop network tests via xharness and webso…
Browse files Browse the repository at this point in the history
…cket (#54289)

Makes it possible to run LoopbackServer tests with WASM
- conditional compilation of LoopbackServer via #if TARGET_BROWSER
- minimal implementation of WebSocketStream for the unit tests
- simple SocketWrapper class to abstract Socket and WebSocket close/dispose/shutdown
- added handling of CORS headers and pre-flight requests as necessary

- new xharness web server middleware
- adding it to helix payload
  • Loading branch information
pavelsavara authored Jun 22, 2021
1 parent 56778f0 commit fe89236
Show file tree
Hide file tree
Showing 41 changed files with 1,028 additions and 228 deletions.
4 changes: 4 additions & 0 deletions src/libraries/Common/tests/System/Net/Configuration.Http.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,15 @@ public static partial class Http
public static string EchoClientCertificateRemoteServer => GetValue("DOTNET_TEST_HTTPHOST_ECHOCLIENTCERT", "https://corefx-net-tls.azurewebsites.net/EchoClientCertificate.ashx");
public static string Http2ForceUnencryptedLoopback => GetValue("DOTNET_TEST_HTTP2_FORCEUNENCRYPTEDLOOPBACK");

public static string RemoteLoopHost => GetValue("DOTNET_TEST_REMOTE_LOOP_HOST");

private const string EchoHandler = "Echo.ashx";
private const string EmptyContentHandler = "EmptyContent.ashx";
private const string RedirectHandler = "Redirect.ashx";
private const string VerifyUploadHandler = "VerifyUpload.ashx";
private const string DeflateHandler = "Deflate.ashx";
private const string GZipHandler = "GZip.ashx";
private const string RemoteLoopHandler = "RemoteLoop";

public static readonly Uri RemoteEchoServer = new Uri("http://" + Host + "/" + EchoHandler);
public static readonly Uri SecureRemoteEchoServer = new Uri("https://" + SecureHost + "/" + EchoHandler);
Expand All @@ -67,6 +70,7 @@ public static partial class Http
public static readonly Uri RemoteGZipServer = new Uri("http://" + Host + "/" + GZipHandler);
public static readonly Uri Http2RemoteDeflateServer = new Uri("https://" + Http2Host + "/" + DeflateHandler);
public static readonly Uri Http2RemoteGZipServer = new Uri("https://" + Http2Host + "/" + GZipHandler);
public static Uri RemoteLoopServer => new Uri("ws://" + RemoteLoopHost + "/" + RemoteLoopHandler);

public static readonly object[][] EchoServers = EchoServerList.Select(x => new object[] { x }).ToArray();
public static readonly object[][] VerifyUploadServers = { new object[] { RemoteVerifyUploadServer }, new object[] { SecureRemoteVerifyUploadServer }, new object[] { Http2RemoteVerifyUploadServer } };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Threading;

namespace System.Net.Test.Common
{
Expand All @@ -20,7 +22,7 @@ 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 Task<GenericLoopbackConnection> CreateConnectionAsync(SocketWrapper socket, Stream stream, GenericLoopbackOptions options = null);

public abstract Version Version { get; }

Expand Down Expand Up @@ -59,6 +61,54 @@ public Task<HttpRequestData> AcceptConnectionSendResponseAndCloseAsync(HttpStatu
}
}

public sealed class SocketWrapper : IDisposable
{
private Socket _socket;
private WebSocket _websocket;

public SocketWrapper(Socket socket)
{
_socket = socket;
}
public SocketWrapper(WebSocket websocket)
{
_websocket = websocket;
}

public void Dispose()
{
_socket?.Dispose();
_websocket?.Dispose();
}
public void Close()
{
_socket?.Close();
CloseWebSocket();
}

public void Shutdown(SocketShutdown how)
{
_socket?.Shutdown(how);
CloseWebSocket();
}

private void CloseWebSocket()
{
if (_websocket != null && (_websocket.State == WebSocketState.Open || _websocket.State == WebSocketState.Connecting || _websocket.State == WebSocketState.None))
{
try
{
var task = _websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "closing remoteLoop", CancellationToken.None);
// Block and wait for the task to complete synchronously
Task.WaitAll(task);
}
catch (Exception)
{
}
}
}
}

public abstract class GenericLoopbackConnection : IDisposable
{
public abstract void Dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class Http2LoopbackConnection : GenericLoopbackConnection
{
public const string Http2Prefix = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";

private Socket _connectionSocket;
private SocketWrapper _connectionSocket;
private Stream _connectionStream;
private TaskCompletionSource<bool> _ignoredSettingsAckPromise;
private bool _ignoreWindowUpdates;
Expand All @@ -34,19 +34,19 @@ public class Http2LoopbackConnection : GenericLoopbackConnection
public Stream Stream => _connectionStream;
public Task<bool> SettingAckWaiter => _ignoredSettingsAckPromise?.Task;

private Http2LoopbackConnection(Socket socket, Stream stream, TimeSpan timeout)
private Http2LoopbackConnection(SocketWrapper socket, Stream stream, TimeSpan timeout)
{
_connectionSocket = socket;
_connectionStream = stream;
_timeout = timeout;
}

public static Task<Http2LoopbackConnection> CreateAsync(Socket socket, Stream stream, Http2Options httpOptions)
public static Task<Http2LoopbackConnection> CreateAsync(SocketWrapper socket, Stream stream, Http2Options httpOptions)
{
return CreateAsync(socket, stream, httpOptions, Http2LoopbackServer.Timeout);
}

public static async Task<Http2LoopbackConnection> CreateAsync(Socket socket, Stream stream, Http2Options httpOptions, TimeSpan timeout)
public static async Task<Http2LoopbackConnection> CreateAsync(SocketWrapper socket, Stream stream, Http2Options httpOptions, TimeSpan timeout)
{
if (httpOptions.UseSsl)
{
Expand Down Expand Up @@ -230,9 +230,9 @@ private async Task<Frame> ReadFrameAsync(CancellationToken cancellationToken)
}

// Reset and return underlying networking objects.
public (Socket, Stream) ResetNetwork()
public (SocketWrapper, Stream) ResetNetwork()
{
Socket oldSocket = _connectionSocket;
SocketWrapper oldSocket = _connectionSocket;
Stream oldStream = _connectionStream;
_connectionSocket = null;
_connectionStream = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ public async Task<Http2LoopbackConnection> AcceptConnectionAsync(TimeSpan? timeo
Socket connectionSocket = await _listenSocket.AcceptAsync().ConfigureAwait(false);

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

return connection;
Expand Down Expand Up @@ -201,7 +202,7 @@ public override GenericLoopbackServer CreateServer(GenericLoopbackOptions option
return Http2LoopbackServer.CreateServer(CreateOptions(options));
}

public override async Task<GenericLoopbackConnection> CreateConnectionAsync(Socket socket, Stream stream, GenericLoopbackOptions options = null)
public override async Task<GenericLoopbackConnection> CreateConnectionAsync(SocketWrapper socket, Stream stream, GenericLoopbackOptions options = null)
{
return await Http2LoopbackConnection.CreateAsync(socket, stream, CreateOptions(options)).ConfigureAwait(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public override async Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Ta
await funcAsync(server, server.Address).WaitAsync(TimeSpan.FromMilliseconds(millisecondsTimeout));
}

public override Task<GenericLoopbackConnection> CreateConnectionAsync(Socket socket, Stream stream, GenericLoopbackOptions options = null)
public override Task<GenericLoopbackConnection> CreateConnectionAsync(SocketWrapper 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ public override async Task<GenericLoopbackConnection> EstablishGenericConnection
if (sslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2)
{
// Do not pass original options so the CreateConnectionAsync won't try to do ALPN again.
return connection = await Http2LoopbackServerFactory.Singleton.CreateConnectionAsync(socket, stream, options).ConfigureAwait(false);
return connection = await Http2LoopbackServerFactory.Singleton.CreateConnectionAsync(new SocketWrapper(socket), stream, options).ConfigureAwait(false);
}
if (sslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http11 ||
sslStream.NegotiatedApplicationProtocol == default)
{
// Do not pass original options so the CreateConnectionAsync won't try to do ALPN again.
return connection = await Http11LoopbackServerFactory.Singleton.CreateConnectionAsync(socket, stream, options).ConfigureAwait(false);
return connection = await Http11LoopbackServerFactory.Singleton.CreateConnectionAsync(new SocketWrapper(socket), stream, options).ConfigureAwait(false);
}
else
{
Expand All @@ -103,11 +103,11 @@ public override async Task<GenericLoopbackConnection> EstablishGenericConnection

if (_options.ClearTextVersion == HttpVersion.Version11)
{
return connection = await Http11LoopbackServerFactory.Singleton.CreateConnectionAsync(socket, stream, options).ConfigureAwait(false);
return connection = await Http11LoopbackServerFactory.Singleton.CreateConnectionAsync(new SocketWrapper(socket), stream, options).ConfigureAwait(false);
}
else if (_options.ClearTextVersion == HttpVersion.Version20)
{
return connection = await Http2LoopbackServerFactory.Singleton.CreateConnectionAsync(socket, stream, options).ConfigureAwait(false);
return connection = await Http2LoopbackServerFactory.Singleton.CreateConnectionAsync(new SocketWrapper(socket), stream, options).ConfigureAwait(false);
}
else
{
Expand Down Expand Up @@ -187,7 +187,7 @@ public override GenericLoopbackServer CreateServer(GenericLoopbackOptions option
return HttpAgnosticLoopbackServer.CreateServer(CreateOptions(options));
}

public override Task<GenericLoopbackConnection> CreateConnectionAsync(Socket socket, Stream stream, GenericLoopbackOptions options = null)
public override Task<GenericLoopbackConnection> CreateConnectionAsync(SocketWrapper socket, Stream stream, GenericLoopbackOptions options = null)
{
// This method is always unacceptable to call for an agnostic server.
throw new NotImplementedException("HttpAgnosticLoopbackServerFactory cannot create connection.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ public static IEnumerable<object[]> Authentication_SocketsHttpHandler_TestData()

[Theory]
[MemberData(nameof(Authentication_TestData))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/39187", TestPlatforms.Browser)]
public async Task HttpClientHandler_Authentication_Succeeds(string authenticateHeader, bool result)
{
if (PlatformDetection.IsWindowsNanoServer)
Expand Down Expand Up @@ -144,7 +143,6 @@ public async Task HttpClientHandler_MultipleAuthenticateHeaders_WithSameAuth_Suc
[Theory]
[InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\n")]
[InlineData("WWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\nWWW-Authenticate: Basic realm=\"hello\"\r\n")]
[ActiveIssue("https://github.com/dotnet/runtime/issues/39187", TestPlatforms.Browser)]
public async Task HttpClientHandler_MultipleAuthenticateHeaders_Succeeds(string authenticateHeader)
{
if (PlatformDetection.IsWindowsNanoServer)
Expand All @@ -164,7 +162,6 @@ await LoopbackServer.CreateServerAsync(async (server, url) =>
[Theory]
[InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: NTLM\r\n", "Basic", "Negotiate")]
[InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\nWWW-Authenticate: NTLM\r\n", "Digest", "Negotiate")]
[ActiveIssue("https://github.com/dotnet/runtime/issues/39187", TestPlatforms.Browser)]
public async Task HttpClientHandler_MultipleAuthenticateHeaders_PicksSupported(string authenticateHeader, string supportedAuth, string unsupportedAuth)
{
if (PlatformDetection.IsWindowsNanoServer)
Expand All @@ -190,7 +187,6 @@ await LoopbackServer.CreateServerAsync(async (server, url) =>
[Theory]
[InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\n")]
[InlineData("WWW-Authenticate: Digest realm=\"hello\", nonce=\"testnonce\"\r\n")]
[ActiveIssue("https://github.com/dotnet/runtime/issues/39187", TestPlatforms.Browser)]
public async Task HttpClientHandler_IncorrectCredentials_Fails(string authenticateHeader)
{
var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
Expand Down Expand Up @@ -228,7 +224,6 @@ public static IEnumerable<object[]> Authentication_TestData()
[InlineData("NTLM")]
[InlineData("Kerberos")]
[InlineData("Negotiate")]
[ActiveIssue("https://github.com/dotnet/runtime/issues/39187", TestPlatforms.Browser)]
public async Task PreAuthenticate_NoPreviousAuthenticatedRequests_NoCredentialsSent(string credCacheScheme)
{
const int NumRequests = 3;
Expand Down Expand Up @@ -271,7 +266,6 @@ await LoopbackServer.CreateClientAndServerAsync(async uri =>
[Theory]
[InlineData(null, "WWW-Authenticate: Basic realm=\"hello\"\r\n")]
[InlineData("Basic", "WWW-Authenticate: Basic realm=\"hello\"\r\n")]
[ActiveIssue("https://github.com/dotnet/runtime/issues/39187", TestPlatforms.Browser)]
public async Task PreAuthenticate_FirstRequestNoHeaderAndAuthenticates_SecondRequestPreauthenticates(string credCacheScheme, string authResponse)
{
await LoopbackServer.CreateClientAndServerAsync(async uri =>
Expand Down Expand Up @@ -364,7 +358,6 @@ await LoopbackServer.CreateClientAndServerAsync(async uri =>
[InlineData((HttpStatusCode)508)] // LoopDetected
[InlineData((HttpStatusCode)510)] // NotExtended
[InlineData((HttpStatusCode)511)] // NetworkAuthenticationRequired
[ActiveIssue("https://github.com/dotnet/runtime/issues/39187", TestPlatforms.Browser)]
public async Task PreAuthenticate_FirstRequestNoHeader_SecondRequestVariousStatusCodes_ThirdRequestPreauthenticates(HttpStatusCode statusCode)
{
const string AuthResponse = "WWW-Authenticate: Basic realm=\"hello\"\r\n";
Expand Down Expand Up @@ -408,7 +401,6 @@ await LoopbackServer.CreateClientAndServerAsync(async uri =>
[InlineData("/something/hello.html", "/world.html", false)]
[InlineData("/something/hello.html", "/another/", false)]
[InlineData("/something/hello.html", "/another/hello.html", false)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/39187", TestPlatforms.Browser)]
public async Task PreAuthenticate_AuthenticatedUrl_ThenTryDifferentUrl_SendsAuthHeaderOnlyIfPrefixMatches(
string originalRelativeUri, string secondRelativeUri, bool expectedAuthHeader)
{
Expand Down Expand Up @@ -448,7 +440,6 @@ await LoopbackServer.CreateClientAndServerAsync(async uri =>
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/39187", TestPlatforms.Browser)]
public async Task PreAuthenticate_SuccessfulBasicButThenFails_DoesntLoopInfinitely()
{
await LoopbackServer.CreateClientAndServerAsync(async uri =>
Expand Down Expand Up @@ -487,7 +478,6 @@ await LoopbackServer.CreateClientAndServerAsync(async uri =>
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/39187", TestPlatforms.Browser)]
public async Task PreAuthenticate_SuccessfulBasic_ThenDigestChallenged()
{
if (IsWinHttpHandler)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
}

[Fact]
[SkipOnPlatform(TestPlatforms.Browser, "MaxConnectionsPerServer is not supported on Browser")]
public async Task MaxConnectionsPerServer_WaitingConnectionsAreCancelable()
{
if (LoopbackServerFactory.Version >= HttpVersion20.Value)
Expand Down
Loading

0 comments on commit fe89236

Please sign in to comment.