diff --git a/KestrelHttpServer.sln b/KestrelHttpServer.sln index 9bc9681d39..c9679e5676 100644 --- a/KestrelHttpServer.sln +++ b/KestrelHttpServer.sln @@ -134,6 +134,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Sockets.B EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Libuv.BindTests", "test\Kestrel.Transport.Libuv.BindTests\Kestrel.Transport.Libuv.BindTests.csproj", "{FB9C6B61-0A7B-4FFA-B772-A754316B262E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.H2Spec.FunctionalTests", "test\Kestrel.H2Spec.FunctionalTests\Kestrel.H2Spec.FunctionalTests.csproj", "{C4123E55-5760-4557-B89B-39E1258FD7F9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -420,6 +422,18 @@ Global {FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Release|x64.Build.0 = Release|Any CPU {FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Release|x86.ActiveCfg = Release|Any CPU {FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Release|x86.Build.0 = Release|Any CPU + {C4123E55-5760-4557-B89B-39E1258FD7F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4123E55-5760-4557-B89B-39E1258FD7F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4123E55-5760-4557-B89B-39E1258FD7F9}.Debug|x64.ActiveCfg = Debug|Any CPU + {C4123E55-5760-4557-B89B-39E1258FD7F9}.Debug|x64.Build.0 = Debug|Any CPU + {C4123E55-5760-4557-B89B-39E1258FD7F9}.Debug|x86.ActiveCfg = Debug|Any CPU + {C4123E55-5760-4557-B89B-39E1258FD7F9}.Debug|x86.Build.0 = Debug|Any CPU + {C4123E55-5760-4557-B89B-39E1258FD7F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4123E55-5760-4557-B89B-39E1258FD7F9}.Release|Any CPU.Build.0 = Release|Any CPU + {C4123E55-5760-4557-B89B-39E1258FD7F9}.Release|x64.ActiveCfg = Release|Any CPU + {C4123E55-5760-4557-B89B-39E1258FD7F9}.Release|x64.Build.0 = Release|Any CPU + {C4123E55-5760-4557-B89B-39E1258FD7F9}.Release|x86.ActiveCfg = Release|Any CPU + {C4123E55-5760-4557-B89B-39E1258FD7F9}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -450,6 +464,7 @@ Global {B5422347-E919-431D-9EF2-C352FFE4D6C1} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} {9254C3EB-196B-402F-A059-34FEA6140500} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} {FB9C6B61-0A7B-4FFA-B772-A754316B262E} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} + {C4123E55-5760-4557-B89B-39E1258FD7F9} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2D10D020-6770-47CA-BB8D-2C23FE3AE071} diff --git a/test/shared/TransportTestHelpers/H2SpecCommands.cs b/test/Kestrel.H2Spec.FunctionalTests/H2SpecCommands.cs similarity index 99% rename from test/shared/TransportTestHelpers/H2SpecCommands.cs rename to test/Kestrel.H2Spec.FunctionalTests/H2SpecCommands.cs index bf33c519a8..09b57d2990 100644 --- a/test/shared/TransportTestHelpers/H2SpecCommands.cs +++ b/test/Kestrel.H2Spec.FunctionalTests/H2SpecCommands.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; using System.Xml; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests +namespace H2Spec.FunctionalTests { public static class H2SpecCommands { diff --git a/test/Kestrel.Transport.FunctionalTests/Http2/H2SpecTests.cs b/test/Kestrel.H2Spec.FunctionalTests/H2SpecTests.cs similarity index 84% rename from test/Kestrel.Transport.FunctionalTests/Http2/H2SpecTests.cs rename to test/Kestrel.H2Spec.FunctionalTests/H2SpecTests.cs index 4d9822cae5..45e66cbf47 100644 --- a/test/Kestrel.Transport.FunctionalTests/Http2/H2SpecTests.cs +++ b/test/Kestrel.H2Spec.FunctionalTests/H2SpecTests.cs @@ -1,33 +1,35 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -#if NETCOREAPP2_2 - +using System; using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging.Testing; using Xunit; using Xunit.Abstractions; -namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 +namespace H2Spec.FunctionalTests { [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")] [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81, SkipReason = "Missing Windows ALPN support: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation#Support")] - public class H2SpecTests : TestApplicationErrorLoggerLoggedTest + public class H2SpecTests : LoggedTest { + private static readonly string _testCertPath = Path.Combine(Directory.GetCurrentDirectory(), "shared", "TestCertificates", "testCert.pfx"); + [ConditionalTheory] [MemberData(nameof(H2SpecTestCases))] public async Task RunIndividualTestCase(H2SpecTestCase testCase) { - var hostBuilder = TransportSelector.GetWebHostBuilder() + var hostBuilder = new WebHostBuilder() .UseKestrel(options => { options.Listen(IPAddress.Loopback, 0, listenOptions => @@ -35,7 +37,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 listenOptions.Protocols = HttpProtocols.Http2; if (testCase.Https) { - listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); + listenOptions.UseHttps(_testCertPath, "testPassword"); } }); }) @@ -46,7 +48,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 { await host.StartAsync(); - H2SpecCommands.RunTest(testCase.Id, host.GetPort(), testCase.Https, Logger); + H2SpecCommands.RunTest(testCase.Id, GetPort(host), testCase.Https, Logger); } } @@ -129,9 +131,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 await context.Response.WriteAsync("Hello World"); }); } + + private static int GetPort(IWebHost host) + { + return host.ServerFeatures.Get().Addresses + .Select(a => new Uri(a)) + .First() + .Port; + } } } -#elif NET461 // HTTP/2 is not supported -#else -#error TFMs need updating -#endif diff --git a/test/Kestrel.H2Spec.FunctionalTests/Kestrel.H2Spec.FunctionalTests.csproj b/test/Kestrel.H2Spec.FunctionalTests/Kestrel.H2Spec.FunctionalTests.csproj new file mode 100644 index 0000000000..5b45607b50 --- /dev/null +++ b/test/Kestrel.H2Spec.FunctionalTests/Kestrel.H2Spec.FunctionalTests.csproj @@ -0,0 +1,28 @@ + + + + H2Spec.FunctionalTests + H2Spec.FunctionalTests + + + $(DeveloperBuildTestTfms) + true + H2Spec.FunctionalTests + + + + + + + + + + + + + + + + + + diff --git a/test/Kestrel.Transport.FunctionalTests/MaxRequestBufferSizeTests.cs b/test/Kestrel.Transport.FunctionalTests/MaxRequestBufferSizeTests.cs index 5090e6d530..ad087574b2 100644 --- a/test/Kestrel.Transport.FunctionalTests/MaxRequestBufferSizeTests.cs +++ b/test/Kestrel.Transport.FunctionalTests/MaxRequestBufferSizeTests.cs @@ -51,7 +51,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests private static readonly string[] _requestLines = new[] { - "POST / HTTP/1.0\r\n", + "POST / HTTP/1.1\r\n", + "Host: \r\n", $"Content-Length: {_dataLength}\r\n", "\r\n" }; @@ -60,11 +61,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { get { + var totalHeaderSize = 0; + + for (var i = 1; i < _requestLines.Length - 1; i++) + { + totalHeaderSize += _requestLines[i].Length; + } + var maxRequestBufferSizeValues = new Tuple[] { - // Smallest buffer that can hold a test request line without causing + // Smallest buffer that can hold the test request headers without causing // the server to hang waiting for the end of the request line or // a header line. - Tuple.Create((long?)(_requestLines.Max(line => line.Length)), true), + Tuple.Create((long?)totalHeaderSize, true), // Small buffer, but large enough to hold all request headers. Tuple.Create((long?)16 * 1024, true), @@ -194,11 +202,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests startReadingRequestBody.TrySetResult(null); } - using (var reader = new StreamReader(stream, Encoding.ASCII)) - { - var response = reader.ReadToEnd(); - Assert.Contains($"bytesRead: {data.Length}", response); - } + await AssertStreamContains(stream, $"bytesRead: {data.Length}"); } } @@ -374,5 +378,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } } + + // THIS IS NOT GENERAL PURPOSE. If the initial characters could repeat, this is broken. However, since we're + // looking for /bytesWritten: \d+/ and the initial "b" cannot occur elsewhere in the pattern, this works. + private static async Task AssertStreamContains(Stream stream, string expectedSubstring) + { + var expectedBytes = Encoding.ASCII.GetBytes(expectedSubstring); + var exptectedLength = expectedBytes.Length; + var responseBuffer = new byte[exptectedLength]; + + var matchedChars = 0; + + while (matchedChars < exptectedLength) + { + var count = await stream.ReadAsync(responseBuffer, 0, exptectedLength - matchedChars).DefaultTimeout(); + + if (count == 0) + { + Assert.True(false, "Stream completed without expected substring."); + } + + for (var i = 0; i < count && matchedChars < exptectedLength; i++) + { + if (responseBuffer[i] == expectedBytes[matchedChars]) + { + matchedChars++; + } + else + { + matchedChars = 0; + } + } + } + } } } diff --git a/test/Kestrel.Transport.FunctionalTests/RequestTests.cs b/test/Kestrel.Transport.FunctionalTests/RequestTests.cs index 347ea3661f..4c1fea6660 100644 --- a/test/Kestrel.Transport.FunctionalTests/RequestTests.cs +++ b/test/Kestrel.Transport.FunctionalTests/RequestTests.cs @@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests // https://github.com/aspnet/KestrelHttpServer/issues/520#issuecomment-188591242 // will be lost. [InlineData((long)int.MaxValue + 1, false)] - public void LargeUpload(long contentLength, bool checkBytes) + public async Task LargeUpload(long contentLength, bool checkBytes) { const int bufferLength = 1024 * 1024; Assert.True(contentLength % bufferLength == 0, $"{nameof(contentLength)} sent must be evenly divisible by {bufferLength}."); @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests total += received; } - await context.Response.WriteAsync(total.ToString(CultureInfo.InvariantCulture)); + await context.Response.WriteAsync($"bytesRead: {total.ToString()}"); }); }); @@ -100,8 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { socket.Connect(new IPEndPoint(IPAddress.Loopback, host.GetPort())); - socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.0\r\n")); - Thread.Sleep(5000); + socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.1\r\nHost: \r\n")); socket.Send(Encoding.ASCII.GetBytes($"Content-Length: {contentLength}\r\n\r\n")); var contentBytes = new byte[bufferLength]; @@ -119,15 +118,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests socket.Send(contentBytes); } - var response = new StringBuilder(); - var responseBytes = new byte[4096]; - var received = 0; - while ((received = socket.Receive(responseBytes)) > 0) + using (var stream = new NetworkStream(socket)) { - response.Append(Encoding.ASCII.GetString(responseBytes, 0, received)); + await AssertStreamContains(stream, $"bytesRead: {contentLength}"); } - - Assert.Contains(contentLength.ToString(CultureInfo.InvariantCulture), response.ToString()); } } } @@ -906,5 +900,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests Assert.NotEmpty(facts["RemotePort"].Value()); } } + + // THIS IS NOT GENERAL PURPOSE. If the initial characters could repeat, this is broken. However, since we're + // looking for /bytesWritten: \d+/ and the initial "b" cannot occur elsewhere in the pattern, this works. + private static async Task AssertStreamContains(Stream stream, string expectedSubstring) + { + var expectedBytes = Encoding.ASCII.GetBytes(expectedSubstring); + var exptectedLength = expectedBytes.Length; + var responseBuffer = new byte[exptectedLength]; + + var matchedChars = 0; + + while (matchedChars < exptectedLength) + { + var count = await stream.ReadAsync(responseBuffer, 0, exptectedLength - matchedChars).DefaultTimeout(); + + if (count == 0) + { + Assert.True(false, "Stream completed without expected substring."); + } + + for (var i = 0; i < count && matchedChars < exptectedLength; i++) + { + if (responseBuffer[i] == expectedBytes[matchedChars]) + { + matchedChars++; + } + else + { + matchedChars = 0; + } + } + } + } } } diff --git a/test/Kestrel.Transport.Libuv.FunctionalTests/Kestrel.Transport.Libuv.FunctionalTests.csproj b/test/Kestrel.Transport.Libuv.FunctionalTests/Kestrel.Transport.Libuv.FunctionalTests.csproj index 7c9eb1fa1d..5169c5ed25 100644 --- a/test/Kestrel.Transport.Libuv.FunctionalTests/Kestrel.Transport.Libuv.FunctionalTests.csproj +++ b/test/Kestrel.Transport.Libuv.FunctionalTests/Kestrel.Transport.Libuv.FunctionalTests.csproj @@ -11,7 +11,6 @@ - diff --git a/test/shared/DiagnosticMemoryPoolFactory.cs b/test/shared/TransportTestHelpers/DiagnosticMemoryPoolFactory.cs similarity index 100% rename from test/shared/DiagnosticMemoryPoolFactory.cs rename to test/shared/TransportTestHelpers/DiagnosticMemoryPoolFactory.cs