Merge pull request #718 from aspnet/bangbingsyb/ancm-stress
IIS ANCM stress test application
This commit is contained in:
commit
2233dcf509
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
<SystemIOPipelinesPackageVersion>4.5.0-preview2-26313-01</SystemIOPipelinesPackageVersion>
|
||||
<SystemManagementAutomationPackageVersion>6.1.7601.17515</SystemManagementAutomationPackageVersion>
|
||||
<SystemMemoryPackageVersion>4.5.0-preview2-26313-01</SystemMemoryPackageVersion>
|
||||
<SystemNetWebSocketsWebSocketProtocolPackageVersion>4.5.0-preview2-26313-01</SystemNetWebSocketsWebSocketProtocolPackageVersion>
|
||||
<SystemNumericsVectorsPackageVersion>4.5.0-preview2-26313-01</SystemNumericsVectorsPackageVersion>
|
||||
<SystemRuntimeCompilerServicesUnsafePackageVersion>4.5.0-preview2-26313-01</SystemRuntimeCompilerServicesUnsafePackageVersion>
|
||||
<SystemSecurityPrincipalWindowsPackageVersion>4.5.0-preview2-26313-01</SystemSecurityPrincipalWindowsPackageVersion>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Server.IISIntegration\Microsoft.AspNetCore.Server.IISIntegration.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||
<PackageReference Include="System.Net.WebSockets.WebSocketProtocol" Version="$(SystemNetWebSocketsWebSocketProtocolPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -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<Startup>()
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<string>().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<IHttpUpgradeFeature>();
|
||||
|
||||
// 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<IApplicationLifetime>();
|
||||
|
||||
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<byte>(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<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, token);
|
||||
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(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<byte>(buffer), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("{0} Exception caught!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<KeyValuePair<string, string>> GenerateResponseHeaders(string key)
|
||||
{
|
||||
yield return new KeyValuePair<string, string>(Constants.Headers.Connection, Constants.Headers.Upgrade);
|
||||
yield return new KeyValuePair<string, string>(Constants.Headers.Upgrade, Constants.Headers.UpgradeWebSocket);
|
||||
yield return new KeyValuePair<string, string>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue