Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 5108d92

Browse files
authored
fix more tests to work with http2 (#39019)
* fix cancelation tests to work with http2 * fix more tests and feedback from review * feedback from review * fix issues with missing connection: close * update body check * feedback from review * fix resolve * feedback from review
1 parent a0279c4 commit 5108d92

File tree

8 files changed

+307
-180
lines changed

8 files changed

+307
-180
lines changed

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

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,40 @@ namespace System.Net.Test.Common
1414

1515
public abstract class LoopbackServerFactory
1616
{
17-
public abstract Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Task> funcAsync, int millisecondsTimeout = 60_000);
17+
public abstract Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Task> funcAsync, int millisecondsTimeout = 60_000, GenericLoopbackOptions options = null);
1818

1919
public abstract bool IsHttp11 { get; }
2020
public abstract bool IsHttp2 { get; }
2121

2222
// Common helper methods
2323

24-
public Task CreateClientAndServerAsync(Func<Uri, Task> clientFunc, Func<GenericLoopbackServer, Task> serverFunc, int millisecondsTimeout = 60_000)
24+
public Task CreateClientAndServerAsync(Func<Uri, Task> clientFunc, Func<GenericLoopbackServer, Task> serverFunc, int millisecondsTimeout = 60_000, GenericLoopbackOptions options = null)
2525
{
2626
return CreateServerAsync(async (server, uri) =>
2727
{
2828
Task clientTask = clientFunc(uri);
2929
Task serverTask = serverFunc(server);
3030

3131
await new Task[] { clientTask, serverTask }.WhenAllOrAnyFailed().ConfigureAwait(false);
32-
}).TimeoutAfter(millisecondsTimeout);
32+
}, options: options).TimeoutAfter(millisecondsTimeout);
3333
}
3434
}
3535

3636
public abstract class GenericLoopbackServer : IDisposable
3737
{
3838
// Accept a new connection, process a single request and send the specified response, and gracefully close the connection.
39-
public abstract Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = null);
39+
public abstract Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "");
4040

4141
// Accept a new connection, and hand it to provided delegate.
4242
public abstract Task AcceptConnectionAsync(Func<GenericLoopbackConnection, Task> funcAsync);
4343

4444
public abstract void Dispose();
45+
46+
// Legacy API.
47+
public Task<HttpRequestData> AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, string content = "", IList<HttpHeaderData> additionalHeaders = null)
48+
{
49+
return HandleRequestAsync(statusCode, headers: additionalHeaders, content: content);
50+
}
4551
}
4652

4753
public abstract class GenericLoopbackConnection : IDisposable
@@ -54,22 +60,26 @@ public abstract class GenericLoopbackConnection : IDisposable
5460
public abstract Task<Byte[]> ReadRequestBodyAsync();
5561

5662
/// <summary>Sends Response back with provided statusCode, headers and content. Can be called multiple times on same response if isFinal was set to false before.</summary>
57-
public abstract Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string body = null, bool isFinal = true, int requestId = 0);
63+
public abstract Task SendResponseAsync(HttpStatusCode? statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true, int requestId = 0);
5864
/// <summary>Sends response headers.</summary>
5965
public abstract Task SendResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, int requestId = 0);
6066
/// <summary>Sends Response body after SendResponse was called with isFinal: false.</summary>
61-
public abstract Task SendResponseBodyAsync(byte[] data, bool isFinal = true, int requestId = 0);
67+
public abstract Task SendResponseBodyAsync(byte[] content, bool isFinal = true, int requestId = 0);
6268

6369
/// <summary>Waits for the client to signal cancellation.</summary>
6470
public abstract Task WaitForCancellationAsync(bool ignoreIncomingData = true, int requestId = 0);
6571

6672
/// <summary>Helper function to make it easier to convert old test with strings.</summary>
67-
public async Task SendResponseBodyAsync(string data, bool isFinal = true, int requestId = 0)
73+
public async Task SendResponseBodyAsync(string content, bool isFinal = true, int requestId = 0)
6874
{
69-
await SendResponseBodyAsync(String.IsNullOrEmpty(data) ? new byte[0] : Encoding.ASCII.GetBytes(data), isFinal, requestId);
75+
await SendResponseBodyAsync(String.IsNullOrEmpty(content) ? new byte[0] : Encoding.ASCII.GetBytes(content), isFinal, requestId);
7076
}
7177
}
7278

79+
public class GenericLoopbackOptions
80+
{
81+
public IPAddress Address { get; set; } = IPAddress.Loopback;
82+
}
7383

7484
public struct HttpHeaderData
7585
{

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

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -669,11 +669,58 @@ public override Task<Byte[]> ReadRequestBodyAsync()
669669
return ReadBodyAsync();
670670
}
671671

672-
public override Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string body = null, bool isFinal = true, int requestId = 0)
672+
public override async Task SendResponseAsync(HttpStatusCode? statusCode = null, IList<HttpHeaderData> headers = null, string body = null, bool isFinal = true, int requestId = 0)
673673
{
674+
// TODO: Header continuation support.
675+
Assert.NotNull(statusCode);
676+
677+
if (headers != null)
678+
{
679+
bool hasDate = false;
680+
bool stripContentLength = false;
681+
foreach (HttpHeaderData headerData in headers)
682+
{
683+
// Check if we should inject Date header to match HTTP/1.
684+
if (headerData.Name.Equals("Date", StringComparison.OrdinalIgnoreCase))
685+
{
686+
hasDate = true;
687+
}
688+
else if (headerData.Name.Equals("Content-Length") && headerData.Value == null)
689+
{
690+
// Hack used for Http/1 to avoid sending content-length header.
691+
stripContentLength = true;
692+
}
693+
}
694+
695+
if (!hasDate || stripContentLength)
696+
{
697+
var newHeaders = new List<HttpHeaderData>();
698+
foreach (HttpHeaderData headerData in headers)
699+
{
700+
if (headerData.Name.Equals("Content-Length") && headerData.Value == null)
701+
{
702+
continue;
703+
}
704+
705+
newHeaders.Add(headerData);
706+
}
707+
newHeaders.Add(new HttpHeaderData("Date", $"{DateTimeOffset.UtcNow:R}"));
708+
headers = newHeaders;
709+
}
710+
}
711+
674712
int streamId = requestId == 0 ? _lastStreamId : requestId;
675713
bool endHeaders = body != null || isFinal;
676-
return SendResponseHeadersAsync(streamId, endStream : isFinal, statusCode, isTrailingHeader : false, endHeaders : endHeaders, headers);
714+
715+
if (string.IsNullOrEmpty(body))
716+
{
717+
await SendResponseHeadersAsync(streamId, endStream: isFinal, (HttpStatusCode)statusCode, endHeaders: endHeaders, headers: headers);
718+
}
719+
else
720+
{
721+
await SendResponseHeadersAsync(streamId, endStream: false, (HttpStatusCode)statusCode, endHeaders: endHeaders, headers: headers);
722+
await SendResponseBodyAsync(body, isFinal: isFinal, requestId: streamId);
723+
}
677724
}
678725

679726
public override Task SendResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, int requestId = 0)

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public override void Dispose()
143143
// GenericLoopbackServer implementation
144144
//
145145

146-
public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = null)
146+
public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
147147
{
148148
Http2LoopbackConnection connection = await EstablishConnectionAsync().ConfigureAwait(false);
149149

@@ -153,7 +153,7 @@ public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode st
153153
// So, send a GOAWAY frame now so the client won't inadvertantly try to reuse the connection.
154154
await connection.SendGoAway(streamId).ConfigureAwait(false);
155155

156-
if (content == null)
156+
if (string.IsNullOrEmpty(content))
157157
{
158158
await connection.SendResponseHeadersAsync(streamId, endStream: true, statusCode, isTrailingHeader: false, headers : headers).ConfigureAwait(false);
159159
}
@@ -188,9 +188,8 @@ public static async Task CreateClientAndServerAsync(Func<Uri, Task> clientFunc,
188188
}
189189
}
190190

191-
public class Http2Options
191+
public class Http2Options : GenericLoopbackOptions
192192
{
193-
public IPAddress Address { get; set; } = IPAddress.Loopback;
194193
public int ListenBacklog { get; set; } = 1;
195194
public bool UseSsl { get; set; } = PlatformDetection.SupportsAlpn && !Capability.Http2ForceUnencryptedLoopback();
196195
public SslProtocols SslProtocols { get; set; } = SslProtocols.Tls12;
@@ -208,9 +207,15 @@ public static async Task CreateServerAsync(Func<Http2LoopbackServer, Uri, Task>
208207
}
209208
}
210209

211-
public override async Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Task> funcAsync, int millisecondsTimeout = 60_000)
210+
public override async Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Task> funcAsync, int millisecondsTimeout = 60_000, GenericLoopbackOptions options = null)
212211
{
213-
using (var server = Http2LoopbackServer.CreateServer())
212+
Http2Options http2Options = new Http2Options();
213+
if (options != null)
214+
{
215+
http2Options.Address = options.Address;
216+
}
217+
218+
using (var server = Http2LoopbackServer.CreateServer(http2Options))
214219
{
215220
await funcAsync(server, server.Address).TimeoutAfter(millisecondsTimeout).ConfigureAwait(false);
216221
}

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

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -122,34 +122,9 @@ await sslStream.AuthenticateAsServerAsync(
122122

123123
public async Task AcceptConnectionAsync(Func<Connection, Task> funcAsync)
124124
{
125-
using (Socket s = await _listenSocket.AcceptAsync().ConfigureAwait(false))
125+
using (Connection connection = await EstablishConnectionAsync().ConfigureAwait(false))
126126
{
127-
s.NoDelay = true;
128-
129-
Stream stream = new NetworkStream(s, ownsSocket: false);
130-
if (_options.UseSsl)
131-
{
132-
var sslStream = new SslStream(stream, false, delegate { return true; });
133-
using (var cert = Configuration.Certificates.GetServerCertificate())
134-
{
135-
await sslStream.AuthenticateAsServerAsync(
136-
cert,
137-
clientCertificateRequired: true, // allowed but not required
138-
enabledSslProtocols: _options.SslProtocols,
139-
checkCertificateRevocation: false).ConfigureAwait(false);
140-
}
141-
stream = sslStream;
142-
}
143-
144-
if (_options.StreamWrapper != null)
145-
{
146-
stream = _options.StreamWrapper(stream);
147-
}
148-
149-
using (var connection = new Connection(s, stream))
150-
{
151-
await funcAsync(connection).ConfigureAwait(false);
152-
}
127+
await funcAsync(connection).ConfigureAwait(false);
153128
}
154129
}
155130

@@ -400,9 +375,8 @@ public static string GetConnectionCloseResponse(HttpStatusCode statusCode = Http
400375
"\r\n" +
401376
content;
402377

403-
public class Options
378+
public class Options : GenericLoopbackOptions
404379
{
405-
public IPAddress Address { get; set; } = IPAddress.Loopback;
406380
public int ListenBacklog { get; set; } = 1;
407381
public bool UseSsl { get; set; } = false;
408382
public SslProtocols SslProtocols { get; set; } =
@@ -761,36 +735,53 @@ public override async Task<Byte[]> ReadRequestBodyAsync()
761735
return buffer;
762736
}
763737

764-
public override async Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = null, bool isFinal = true, int requestId = 0)
738+
public override async Task SendResponseAsync(HttpStatusCode? statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = null, bool isFinal = true, int requestId = 0)
765739
{
766740
string headerString = null;
767741
int contentLength = -1;
742+
bool isChunked = false;
743+
bool hasContentLength = false;
768744

769745
if (headers != null)
770746
{
747+
// Process given headers and look for some well-known cases.
771748
foreach (HttpHeaderData headerData in headers)
772749
{
773-
headerString = headerString + $"{headerData.Name}: {headerData.Value}\r\n";
774750
if (headerData.Name.Equals("Content-Length", StringComparison.OrdinalIgnoreCase))
775751
{
752+
hasContentLength = true;
753+
if (headerData.Value == null)
754+
{
755+
continue;
756+
}
757+
776758
contentLength = int.Parse(headerData.Value);
777759
}
760+
else if (headerData.Name.Equals("Transfer-Encoding", StringComparison.OrdinalIgnoreCase) && headerData.Value.Equals("chunked", StringComparison.OrdinalIgnoreCase))
761+
{
762+
isChunked = true;
763+
}
764+
765+
headerString = headerString + $"{headerData.Name}: {headerData.Value}\r\n";
778766
}
779767
}
780768

781-
if (contentLength < 0)
769+
bool endHeaders = content != null || isFinal;
770+
if (statusCode != null)
782771
{
783-
// We did not find Content header in headers.
784-
contentLength = String.IsNullOrEmpty(content) ? 0 : content.Length;
772+
// If we need to send status line, prepped it to headers and possibly add missing headers to the end.
773+
headerString =
774+
$"HTTP/1.1 {(int)statusCode} {GetStatusDescription((HttpStatusCode)statusCode)}\r\n" +
775+
(!hasContentLength && !isChunked && content != null ? $"Content-length: {content.Length}\r\n" : "") +
776+
headerString +
777+
(endHeaders ? "\r\n" : "");
785778
}
786779

787-
if (content != null || isFinal)
780+
await SendResponseAsync(headerString).ConfigureAwait(false);
781+
if (content != null)
788782
{
789-
headerString = GetHttpResponseHeaders(statusCode, headerString, contentLength, connectionClose: true);
783+
await SendResponseBodyAsync(content, isFinal: isFinal, requestId: requestId).ConfigureAwait(false);
790784
}
791-
792-
await SendResponseAsync(headerString).ConfigureAwait(false);
793-
await SendResponseAsync(content).ConfigureAwait(false);
794785
}
795786

796787
public override async Task SendResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, int requestId = 0)
@@ -835,13 +826,34 @@ public override async Task WaitForCancellationAsync(bool ignoreIncomingData = tr
835826
}
836827
}
837828

838-
public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = null)
829+
public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
839830
{
840831
using (Connection connection = await EstablishConnectionAsync().ConfigureAwait(false))
841832
{
842833
HttpRequestData requestData = await connection.ReadRequestDataAsync().ConfigureAwait(false);
843-
await connection.SendResponseAsync(statusCode, headers, content: content).ConfigureAwait(false);
844834

835+
// For historical reasons, we added Date and "Connection: close" (to improve test reliability)
836+
bool hasDate = false;
837+
List<HttpHeaderData> newHeaders = new List<HttpHeaderData>();
838+
if (headers != null)
839+
{
840+
foreach (var header in headers)
841+
{
842+
newHeaders.Add(header);
843+
if (header.Name.Equals("Date", StringComparison.OrdinalIgnoreCase))
844+
{
845+
hasDate = true;
846+
}
847+
}
848+
}
849+
850+
newHeaders.Add(new HttpHeaderData("Connection", "Close"));
851+
if (!hasDate)
852+
{
853+
newHeaders.Add(new HttpHeaderData("Date", "{DateTimeOffset.UtcNow:R}"));
854+
}
855+
856+
await connection.SendResponseAsync(statusCode, newHeaders, content: content).ConfigureAwait(false);
845857
return requestData;
846858
}
847859
}
@@ -856,9 +868,15 @@ public sealed class Http11LoopbackServerFactory : LoopbackServerFactory
856868
{
857869
public static readonly Http11LoopbackServerFactory Singleton = new Http11LoopbackServerFactory();
858870

859-
public override Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Task> funcAsync, int millisecondsTimeout = 60_000)
871+
public override Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Task> funcAsync, int millisecondsTimeout = 60_000, GenericLoopbackOptions options = null)
860872
{
861-
return LoopbackServer.CreateServerAsync((server, uri) => funcAsync(server, uri));
873+
LoopbackServer.Options newOptions = new LoopbackServer.Options();
874+
if (options != null)
875+
{
876+
newOptions.Address = options.Address;
877+
}
878+
879+
return LoopbackServer.CreateServerAsync((server, uri) => funcAsync(server, uri), options: newOptions);
862880
}
863881

864882
public override bool IsHttp11 => true;

0 commit comments

Comments
 (0)