diff --git a/IISIntegration.sln b/IISIntegration.sln
index 636f15279e..4086f09be5 100644
--- a/IISIntegration.sln
+++ b/IISIntegration.sln
@@ -72,6 +72,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCoreModuleTests", "test\AspNetCoreModuleTests\AspNetCoreModuleTests.vcxproj", "{0692D963-DB10-4387-B3EA-460FBB9BD9A3}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ANCMStressTestApp", "test\ANCMStressTestApp\ANCMStressTestApp.csproj", "{13FD8F12-FFBE-4D01-B4AC-444F2994B04F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -228,6 +230,18 @@ Global
{0692D963-DB10-4387-B3EA-460FBB9BD9A3}.Release|x64.Build.0 = Release|x64
{0692D963-DB10-4387-B3EA-460FBB9BD9A3}.Release|x86.ActiveCfg = Release|Win32
{0692D963-DB10-4387-B3EA-460FBB9BD9A3}.Release|x86.Build.0 = Release|Win32
+ {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|x64.Build.0 = Debug|Any CPU
+ {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|x86.Build.0 = Debug|Any CPU
+ {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|x64.ActiveCfg = Release|Any CPU
+ {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|x64.Build.0 = Release|Any CPU
+ {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|x86.ActiveCfg = Release|Any CPU
+ {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -246,6 +260,7 @@ Global
{D57EA297-6DC2-4BC0-8C91-334863327863} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD}
{46A8612B-418B-4D70-B3A7-A21DD0627473} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD}
{0692D963-DB10-4387-B3EA-460FBB9BD9A3} = {EF30B533-D715-421A-92B7-92FEF460AC9C}
+ {13FD8F12-FFBE-4D01-B4AC-444F2994B04F} = {EF30B533-D715-421A-92B7-92FEF460AC9C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DB4F868D-E1AE-4FD7-9333-69FA15B268C5}
diff --git a/build/dependencies.props b/build/dependencies.props
index 45eb8e351b..07933c585a 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -37,6 +37,7 @@
4.5.0-preview2-26313-01
6.1.7601.17515
4.5.0-preview2-26313-01
+ 4.5.0-preview2-26313-01
4.5.0-preview2-26313-01
4.5.0-preview2-26313-01
4.5.0-preview2-26313-01
diff --git a/test/ANCMStressTestApp/ANCMStressTestApp.csproj b/test/ANCMStressTestApp/ANCMStressTestApp.csproj
new file mode 100644
index 0000000000..54755b4824
--- /dev/null
+++ b/test/ANCMStressTestApp/ANCMStressTestApp.csproj
@@ -0,0 +1,18 @@
+
+
+
+ netcoreapp2.1
+ win-x86;win-x64
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/ANCMStressTestApp/Program.cs b/test/ANCMStressTestApp/Program.cs
new file mode 100644
index 0000000000..b0edb5b7f2
--- /dev/null
+++ b/test/ANCMStressTestApp/Program.cs
@@ -0,0 +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.
+
+using System;
+using System.Linq;
+using System.Threading;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Server.IISIntegration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace ANCMStressTestApp
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var host = new WebHostBuilder()
+ .ConfigureLogging((_, factory) =>
+ {
+ factory.AddConsole();
+ })
+ .UseKestrel()
+ .UseIISIntegration()
+ .UseStartup()
+ .Build();
+
+ host.Run();
+ }
+ }
+}
diff --git a/test/ANCMStressTestApp/Properties/launchSettings.json b/test/ANCMStressTestApp/Properties/launchSettings.json
new file mode 100644
index 0000000000..8e9b6cfcf7
--- /dev/null
+++ b/test/ANCMStressTestApp/Properties/launchSettings.json
@@ -0,0 +1,27 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:16606/",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "ANCMStressTestSample": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:16607/"
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/ANCMStressTestApp/Startup.cs b/test/ANCMStressTestApp/Startup.cs
new file mode 100644
index 0000000000..be0969ec77
--- /dev/null
+++ b/test/ANCMStressTestApp/Startup.cs
@@ -0,0 +1,231 @@
+// 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.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Text;
+using System.Net.WebSockets;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Net.Http.Headers;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Primitives;
+
+namespace ANCMStressTestApp
+{
+ public class Startup
+ {
+ // This method gets called by the runtime. Use this method to add services to the container.
+ // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
+ public void ConfigureServices(IServiceCollection services)
+ {
+ }
+
+ public void Configure(IApplicationBuilder app)
+ {
+ app.Map("/HelloWorld", HelloWorld);
+ app.Map("/ConnectionClose", ConnectionClose);
+ app.Map("/EchoPostData", EchoPostData);
+ app.Map("/LargeResponseBody", LargeResponseBody);
+ app.Map("/ResponseHeaders", ResponseHeaders);
+ app.Map("/EnvironmentVariables", EnvironmentVariables);
+ app.Map("/RequestInformation", RequestInformation);
+ app.Map("/WebSocket", WebSocket);
+
+ app.Run(async context =>
+ {
+ await context.Response.WriteAsync("Default Page");
+ });
+ }
+
+ private void HelloWorld(IApplicationBuilder app)
+ {
+ app.Run(async context =>
+ {
+ await context.Response.WriteAsync("Hello World");
+ });
+ }
+
+ private void ConnectionClose(IApplicationBuilder app)
+ {
+ app.Run(async context =>
+ {
+ context.Response.Headers[HeaderNames.Connection] = "close";
+ await context.Response.WriteAsync("Connnection Close");
+ await context.Response.Body.FlushAsync();
+ });
+ }
+
+ private void EchoPostData(IApplicationBuilder app)
+ {
+ app.Run(async context =>
+ {
+ string responseBody = string.Empty;
+
+ if (string.Equals(context.Request.Method, "POST", StringComparison.OrdinalIgnoreCase))
+ {
+ using (StreamReader reader = new StreamReader(context.Request.Body, Encoding.UTF8))
+ {
+ responseBody = await reader.ReadToEndAsync();
+ }
+ }
+ else
+ {
+ responseBody = "NoAction";
+ }
+
+ await context.Response.WriteAsync(responseBody);
+ });
+ }
+
+ private void LargeResponseBody(IApplicationBuilder app)
+ {
+ app.Run(async context =>
+ {
+ if (int.TryParse(context.Request.Query["length"], out var length))
+ {
+ await context.Response.WriteAsync(new string('a', length));
+ }
+ });
+ }
+
+ private void ResponseHeaders(IApplicationBuilder app)
+ {
+ app.Run(async context =>
+ {
+ context.Response.Headers["UnknownHeader"] = "test123=foo";
+ context.Response.ContentType = "text/plain";
+ context.Response.Headers["MultiHeader"] = new StringValues(new string[] { "1", "2" });
+ await context.Response.WriteAsync("Request Complete");
+ });
+ }
+
+ private void EnvironmentVariables(IApplicationBuilder app)
+ {
+ app.Run(async context =>
+ {
+ context.Response.ContentType = "text/plain";
+ await context.Response.WriteAsync("Environment Variables:" + Environment.NewLine);
+ var vars = Environment.GetEnvironmentVariables();
+ foreach (var key in vars.Keys.Cast().OrderBy(key => key, StringComparer.OrdinalIgnoreCase))
+ {
+ var value = vars[key];
+ await context.Response.WriteAsync(key + ": " + value + Environment.NewLine);
+ }
+ await context.Response.WriteAsync(Environment.NewLine);
+ });
+ }
+
+ private void RequestInformation(IApplicationBuilder app)
+ {
+ app.Run(async context =>
+ {
+ context.Response.ContentType = "text/plain";
+
+ await context.Response.WriteAsync("Address:" + Environment.NewLine);
+ await context.Response.WriteAsync("Scheme: " + context.Request.Scheme + Environment.NewLine);
+ await context.Response.WriteAsync("Host: " + context.Request.Headers["Host"] + Environment.NewLine);
+ await context.Response.WriteAsync("PathBase: " + context.Request.PathBase.Value + Environment.NewLine);
+ await context.Response.WriteAsync("Path: " + context.Request.Path.Value + Environment.NewLine);
+ await context.Response.WriteAsync("Query: " + context.Request.QueryString.Value + Environment.NewLine);
+ await context.Response.WriteAsync(Environment.NewLine);
+
+ await context.Response.WriteAsync("Connection:" + Environment.NewLine);
+ await context.Response.WriteAsync("RemoteIp: " + context.Connection.RemoteIpAddress + Environment.NewLine);
+ await context.Response.WriteAsync("RemotePort: " + context.Connection.RemotePort + Environment.NewLine);
+ await context.Response.WriteAsync("LocalIp: " + context.Connection.LocalIpAddress + Environment.NewLine);
+ await context.Response.WriteAsync("LocalPort: " + context.Connection.LocalPort + Environment.NewLine);
+ await context.Response.WriteAsync(Environment.NewLine);
+
+ await context.Response.WriteAsync("Headers:" + Environment.NewLine);
+ foreach (var header in context.Request.Headers)
+ {
+ await context.Response.WriteAsync(header.Key + ": " + header.Value + Environment.NewLine);
+ }
+ await context.Response.WriteAsync(Environment.NewLine);
+ });
+ }
+
+ private void WebSocket(IApplicationBuilder app)
+ {
+ app.Run(async context =>
+ {
+ var upgradeFeature = context.Features.Get();
+
+ // Generate WebSocket response headers
+ string key = context.Request.Headers[Constants.Headers.SecWebSocketKey].ToString();
+ var responseHeaders = HandshakeHelpers.GenerateResponseHeaders(key);
+ foreach (var headerPair in responseHeaders)
+ {
+ context.Response.Headers[headerPair.Key] = headerPair.Value;
+ }
+
+ // Upgrade the connection
+ Stream opaqueTransport = await upgradeFeature.UpgradeAsync();
+
+ // Get the WebSocket object
+ var ws = WebSocketProtocol.CreateFromStream(opaqueTransport, isServer: true, subProtocol: null, keepAliveInterval: TimeSpan.FromMinutes(2));
+
+ var appLifetime = app.ApplicationServices.GetRequiredService();
+
+ await Echo(ws, appLifetime.ApplicationStopping);
+ });
+ }
+
+ private async Task Echo(WebSocket webSocket, CancellationToken token)
+ {
+ try
+ {
+ var buffer = new byte[1024 * 4];
+ var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), token);
+ bool closeFromServer = false;
+ string closeFromServerCmd = "CloseFromServer";
+ int closeFromServerLength = closeFromServerCmd.Length;
+
+ while (!result.CloseStatus.HasValue && !token.IsCancellationRequested && !closeFromServer)
+ {
+ if (result.Count == closeFromServerLength &&
+ Encoding.ASCII.GetString(buffer).Substring(0, result.Count) == closeFromServerCmd)
+ {
+ // The client sent "CloseFromServer" text message to request the server to close (a test scenario).
+ closeFromServer = true;
+ }
+ else
+ {
+ await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, token);
+ result = await webSocket.ReceiveAsync(new ArraySegment(buffer), token);
+ }
+ }
+
+ if (result.CloseStatus.HasValue)
+ {
+ // Client-initiated close handshake
+ await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
+ }
+ else
+ {
+ // Server-initiated close handshake due to either of the two conditions:
+ // (1) The applicaton host is performing a graceful shutdown.
+ // (2) The client sent "CloseFromServer" text message to request the server to close (a test scenario).
+ await webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, closeFromServerCmd, CancellationToken.None);
+
+ // The server has sent the Close frame.
+ // Stop sending but keep receiving until we get the Close frame from the client.
+ while (!result.CloseStatus.HasValue)
+ {
+ result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("{0} Exception caught!", e);
+ }
+ }
+ }
+}
diff --git a/test/ANCMStressTestApp/WebSockets/Constants.cs b/test/ANCMStressTestApp/WebSockets/Constants.cs
new file mode 100644
index 0000000000..bcf5462558
--- /dev/null
+++ b/test/ANCMStressTestApp/WebSockets/Constants.cs
@@ -0,0 +1,17 @@
+// 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.
+
+namespace ANCMStressTestApp
+{
+ public static class Constants
+ {
+ public static class Headers
+ {
+ public const string Upgrade = "Upgrade";
+ public const string UpgradeWebSocket = "websocket";
+ public const string Connection = "Connection";
+ public const string SecWebSocketKey = "Sec-WebSocket-Key";
+ public const string SecWebSocketAccept = "Sec-WebSocket-Accept";
+ }
+ }
+}
diff --git a/test/ANCMStressTestApp/WebSockets/HandshakeHelpers.cs b/test/ANCMStressTestApp/WebSockets/HandshakeHelpers.cs
new file mode 100644
index 0000000000..331f415013
--- /dev/null
+++ b/test/ANCMStressTestApp/WebSockets/HandshakeHelpers.cs
@@ -0,0 +1,42 @@
+// 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.
+
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace ANCMStressTestApp
+{
+ // Removed all the
+ internal static class HandshakeHelpers
+ {
+ public static IEnumerable> GenerateResponseHeaders(string key)
+ {
+ yield return new KeyValuePair(Constants.Headers.Connection, Constants.Headers.Upgrade);
+ yield return new KeyValuePair(Constants.Headers.Upgrade, Constants.Headers.UpgradeWebSocket);
+ yield return new KeyValuePair(Constants.Headers.SecWebSocketAccept, CreateResponseKey(key));
+ }
+
+ public static string CreateResponseKey(string requestKey)
+ {
+ // "The value of this header field is constructed by concatenating /key/, defined above in step 4
+ // in Section 4.2.2, with the string "258EAFA5- E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of
+ // this concatenated value to obtain a 20-byte value and base64-encoding"
+ // https://tools.ietf.org/html/rfc6455#section-4.2.2
+
+ if (requestKey == null)
+ {
+ throw new ArgumentNullException(nameof(requestKey));
+ }
+
+ using (var algorithm = SHA1.Create())
+ {
+ string merged = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ byte[] mergedBytes = Encoding.UTF8.GetBytes(merged);
+ byte[] hashedBytes = algorithm.ComputeHash(mergedBytes);
+ return Convert.ToBase64String(hashedBytes);
+ }
+ }
+ }
+}