ANCM stress app
This commit is contained in:
parent
f1058f8575
commit
0f622ec53d
|
|
@ -0,0 +1,19 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<AssemblyName>ANCMStressTestApp</AssemblyName>
|
||||||
|
<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,43 @@
|
||||||
|
// 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 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 IApplicationLifetime AppLifetime;
|
||||||
|
public static bool AppLifetimeStopping = false;
|
||||||
|
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var host = new WebHostBuilder()
|
||||||
|
.ConfigureLogging((_, factory) =>
|
||||||
|
{
|
||||||
|
factory.AddConsole();
|
||||||
|
})
|
||||||
|
.UseKestrel()
|
||||||
|
.UseStartup<Startup>()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
AppLifetime = (IApplicationLifetime)host.Services.GetService(typeof(IApplicationLifetime));
|
||||||
|
AppLifetime.ApplicationStopping.Register(
|
||||||
|
() => {
|
||||||
|
AppLifetimeStopping = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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,213 @@
|
||||||
|
// 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 = string.Join(", ", context.Request.Headers[Constants.Headers.SecWebSocketKey]);
|
||||||
|
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));
|
||||||
|
|
||||||
|
await Echo(ws);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Echo(WebSocket webSocket)
|
||||||
|
{
|
||||||
|
var buffer = new byte[1024 * 4];
|
||||||
|
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||||
|
bool closeFromServer = false;
|
||||||
|
string closeFromServerCmd = "CloseFromServer";
|
||||||
|
int closeFromServerLength = closeFromServerCmd.Length;
|
||||||
|
|
||||||
|
while (!result.CloseStatus.HasValue)
|
||||||
|
{
|
||||||
|
if ((result.Count == closeFromServerLength && System.Text.Encoding.ASCII.GetString(buffer).Substring(0, result.Count) == closeFromServerCmd)
|
||||||
|
|| Program.AppLifetimeStopping == true)
|
||||||
|
{
|
||||||
|
// start closing handshake from backend process when client send "CloseFromServer" text message
|
||||||
|
// or when any message is sent from client during the graceful shutdown.
|
||||||
|
closeFromServer = true;
|
||||||
|
await webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, closeFromServerCmd, CancellationToken.None);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!closeFromServer)
|
||||||
|
{
|
||||||
|
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
webSocket.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// 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 ConnectionUpgrade = "Upgrade";
|
||||||
|
public const string SecWebSocketKey = "Sec-WebSocket-Key";
|
||||||
|
public const string SecWebSocketVersion = "Sec-WebSocket-Version";
|
||||||
|
public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol";
|
||||||
|
public const string SecWebSocketAccept = "Sec-WebSocket-Accept";
|
||||||
|
public const string SupportedVersion = "13";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.ConnectionUpgrade);
|
||||||
|
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