lfs: false
- bash: |
- docker build -t $(sdkBaseImage) -f $(Build.SourcesDirectory)/$(HttpStressProject)/corefx.Dockerfile .
+ docker build -t $(sdkBaseImage) --build-arg CONFIGURATION=$(BUILD_CONFIGURATION) -f $(Build.SourcesDirectory)/$(HttpStressProject)/corefx.Dockerfile .
displayName: Build Corefx
- bash: |
cd '$(Build.SourcesDirectory)/$(HttpStressProject)'
- docker build -t $(httpStressImage) --build-arg SDK_BASE_IMAGE=$(sdkBaseImage) .
+ docker build -t $(httpStressImage) --build-arg SDK_BASE_IMAGE=$(sdkBaseImage) --build-arg CONFIGURATION=$(BUILD_CONFIGURATION) .
displayName: Build HttpStress
- bash: |
- docker run --rm -e HTTPSTRESS_ARGS='$(HTTPSTRESS_ARGS)' $(httpStressImage)
+ cd '$(Build.SourcesDirectory)/$(HttpStressProject)'
+ export HTTPSTRESS_ARGS='$(HTTPSTRESS_ARGS)'
+ docker-compose up --abort-on-container-exit --no-color
displayName: Run HttpStress
public class Configuration
{
- public Uri ServerUri { get; set; } = new Uri("http://placeholder");
+ public string ServerUri { get; set; } = "";
public RunMode RunMode { get; set; }
public bool ListOperations { get; set; }
WORKDIR /app
COPY . .
+ARG CONFIGURATION=Release
+RUN dotnet build -c $CONFIGURATION
+
+EXPOSE 5001
+
+ENV CONFIGURATION=$CONFIGURATION
ENV HTTPSTRESS_ARGS='-maxExecutionTime 30 -displayInterval 60'
-CMD dotnet run -c Release -- $HTTPSTRESS_ARGS
+CMD dotnet run --no-build -c $CONFIGURATION -- $HTTPSTRESS_ARGS
{
var cmd = new RootCommand();
cmd.AddOption(new Option("-n", "Max number of requests to make concurrently.") { Argument = new Argument<int>("numWorkers", Environment.ProcessorCount) });
- cmd.AddOption(new Option("-serverUri", "Stress suite server uri.") { Argument = new Argument<Uri>("serverUri", new Uri("https://localhost:5001")) });
+ cmd.AddOption(new Option("-serverUri", "Stress suite server uri.") { Argument = new Argument<string>("serverUri", "https://localhost:5001") });
cmd.AddOption(new Option("-runMode", "Stress suite execution mode. Defaults to Both.") { Argument = new Argument<RunMode>("runMode", RunMode.both) });
cmd.AddOption(new Option("-maxExecutionTime", "Maximum stress execution time, in minutes. Defaults to infinity.") { Argument = new Argument<double?>("minutes", null) });
cmd.AddOption(new Option("-maxContentLength", "Max content length for request and response bodies.") { Argument = new Argument<int>("numBytes", 1000) });
config = new Configuration()
{
RunMode = cmdline.ValueForOption<RunMode>("-runMode"),
- ServerUri = cmdline.ValueForOption<Uri>("-serverUri"),
+ ServerUri = cmdline.ValueForOption<string>("-serverUri"),
ListOperations = cmdline.ValueForOption<bool>("-listOps"),
HttpVersion = cmdline.ValueForOption<Version>("-http"),
return ExitCode.CliError;
}
- if (!config.ServerUri.Scheme.StartsWith("http"))
+ if (!config.ServerUri.StartsWith("http"))
{
Console.Error.WriteLine("Invalid server uri");
return ExitCode.CliError;
private const string UNENCRYPTED_HTTP2_ENV_VAR = "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2UNENCRYPTEDSUPPORT";
private readonly (string name, Func<RequestContext, Task> operation)[] _clientOperations;
+ private readonly Uri _baseAddress;
private readonly Configuration _config;
private readonly StressResultAggregator _aggregator;
private readonly Stopwatch _stopwatch = new Stopwatch();
{
_clientOperations = clientOperations;
_config = configuration;
+ _baseAddress = new Uri(configuration.ServerUri);
_aggregator = new StressResultAggregator(clientOperations);
}
private async Task StartCore()
{
- if (_config.ServerUri.Scheme == "http")
+ if (_baseAddress.Scheme == "http")
{
Environment.SetEnvironmentVariable(UNENCRYPTED_HTTP2_ENV_VAR, "1");
}
}
}
- using var client = new HttpClient(CreateHttpHandler()) { BaseAddress = _config.ServerUri, Timeout = _config.DefaultTimeout };
+ HttpClient CreateHttpClient() =>
+ new HttpClient(CreateHttpHandler())
+ {
+ BaseAddress = _baseAddress,
+ Timeout = _config.DefaultTimeout,
+ DefaultRequestVersion = _config.HttpVersion,
+ };
+
+ using HttpClient client = CreateHttpClient();
+
+ // Before starting the full-blown test, make sure can communicate with the server
+ // Needed for scenaria where we're deploying server & client in separate containers, simultaneously.
+ await SendTestRequestToServer(maxRetries: 10);
+
+ // Spin up a thread dedicated to outputting stats for each defined interval
+ new Thread(() =>
+ {
+ while (!_cts.IsCancellationRequested)
+ {
+ Thread.Sleep(_config.DisplayInterval);
+ lock (Console.Out) { _aggregator.PrintCurrentResults(_stopwatch.Elapsed); }
+ }
+ })
+ { IsBackground = true }.Start();
+
+ // Start N workers, each of which sits in a loop making requests.
+ Task[] tasks = Enumerable.Range(0, _config.ConcurrentRequests).Select(RunWorker).ToArray();
+ await Task.WhenAll(tasks);
async Task RunWorker(int taskNum)
{
return ((int)rol5 + h1) ^ h2;
}
}
-
- // Spin up a thread dedicated to outputting stats for each defined interval
- new Thread(() =>
+
+ async Task SendTestRequestToServer(int maxRetries)
{
- while (!_cts.IsCancellationRequested)
+ using HttpClient client = CreateHttpClient();
+ for (int remainingRetries = maxRetries; ; remainingRetries--)
{
- Thread.Sleep(_config.DisplayInterval);
- lock (Console.Out) { _aggregator.PrintCurrentResults(_stopwatch.Elapsed); }
+ try
+ {
+ await client.GetAsync("/");
+ break;
+ }
+ catch (HttpRequestException) when (remainingRetries > 0)
+ {
+ Console.WriteLine($"Stress client could not connect to host {_baseAddress}, {remainingRetries} attempts remaining");
+ await Task.Delay(millisecondsDelay: 1000);
+ }
}
- })
- { IsBackground = true }.Start();
-
- // Start N workers, each of which sits in a loop making requests.
- Task[] tasks = Enumerable.Range(0, _config.ConcurrentRequests).Select(RunWorker).ToArray();
- await Task.WhenAll(tasks);
+ }
}
/// <summary>Aggregate view of a particular stress failure type</summary>
using System.IO;
using System.Linq;
using System.Net;
+using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
private EventListener? _eventListener;
private readonly IWebHost _webHost;
- public Uri ServerUri { get; }
+ public string ServerUri { get; }
public StressServer(Configuration configuration)
{
ServerUri = configuration.ServerUri;
+ (string scheme, string hostname, int port) = ParseServerUri(configuration.ServerUri);
IWebHostBuilder host = WebHost.CreateDefaultBuilder();
if (configuration.UseHttpSys)
// 3. Register the cert, e.g. netsh http add sslcert ipport=[::1]:5001 certhash=THUMBPRINTFROMABOVE appid="{some-guid}"
host = host.UseHttpSys(hso =>
{
- hso.UrlPrefixes.Add(ServerUri.ToString());
+ hso.UrlPrefixes.Add(ServerUri);
hso.Authentication.Schemes = Microsoft.AspNetCore.Server.HttpSys.AuthenticationSchemes.None;
hso.Authentication.AllowAnonymous = true;
hso.MaxConnections = null;
ko.Limits.Http2.InitialConnectionWindowSize = configuration.ServerInitialConnectionWindowSize ?? ko.Limits.Http2.InitialConnectionWindowSize;
ko.Limits.Http2.MaxRequestHeaderFieldSize = configuration.ServerMaxRequestHeaderFieldSize ?? ko.Limits.Http2.MaxRequestHeaderFieldSize;
- IPAddress iPAddress = Dns.GetHostAddresses(configuration.ServerUri.Host).First();
+ switch (hostname)
+ {
+ case "+":
+ case "*":
+ ko.ListenAnyIP(port, ConfigureListenOptions);
+ break;
+ default:
+ IPAddress iPAddress = Dns.GetHostAddresses(hostname).First();
+ ko.Listen(iPAddress, port, ConfigureListenOptions);
+ break;
+
+ }
- ko.Listen(iPAddress, configuration.ServerUri.Port, listenOptions =>
+ void ConfigureListenOptions(ListenOptions listenOptions)
{
- if (configuration.ServerUri.Scheme == "https")
+ if (scheme == "https")
{
// Create self-signed cert for server.
using (RSA rsa = RSA.Create())
{
- var certReq = new CertificateRequest($"CN={ServerUri.Host}", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+ var certReq = new CertificateRequest("CN=contoso.com", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
certReq.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
certReq.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false));
certReq.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false));
HttpProtocols.Http2 :
HttpProtocols.Http1 ;
}
- });
+ }
});
};
}
}
+ private static (string scheme, string hostname, int port) ParseServerUri(string serverUri)
+ {
+ try
+ {
+ var uri = new Uri(serverUri);
+ return (uri.Scheme, uri.Host, uri.Port);
+ }
+ catch (UriFormatException)
+ {
+ // Simple uri parser: used to parse values valid in Kestrel
+ // but not representable by the System.Uri class, e.g. https://+:5050
+ Match m = Regex.Match(serverUri, "^(?<scheme>https?)://(?<host>[^:/]+)(:(?<port>[0-9]+))?");
+
+ if (!m.Success) throw;
+
+ string scheme = m.Groups["scheme"].Value;
+ string hostname = m.Groups["host"].Value;
+ int port = m.Groups["port"].Success ? int.Parse(m.Groups["port"].Value) : (scheme == "https" ? 443 : 80);
+ return (scheme, hostname, port);
+ }
+ }
+
/// <summary>EventListener that dumps HTTP events out to either the console or a stream writer.</summary>
private sealed class HttpEventListener : EventListener
{
return true;
}
-
}
}
WORKDIR /repo
COPY . .
-ARG CONFIG=Release
-RUN ./build.sh -c $CONFIG
+ARG CONFIGURATION=Release
+RUN ./build.sh -c $CONFIGURATION
FROM $SDK_BASE_IMAGE as target
ARG TFM=netcoreapp
ARG OS=Linux
ARG ARCH=x64
-ARG CONFIG=Release
+ARG CONFIGURATION=Release
ARG COREFX_SHARED_FRAMEWORK_NAME=Microsoft.NETCore.App
ARG SOURCE_COREFX_VERSION=5.0.0
ARG TARGET_COREFX_VERSION=3.0.0
COPY --from=corefxbuild \
- $TESTHOST_LOCATION/$TFM-$OS-$CONFIG-$ARCH/shared/$COREFX_SHARED_FRAMEWORK_NAME/$SOURCE_COREFX_VERSION/* \
+ $TESTHOST_LOCATION/$TFM-$OS-$CONFIGURATION-$ARCH/shared/$COREFX_SHARED_FRAMEWORK_NAME/$SOURCE_COREFX_VERSION/* \
$TARGET_SHARED_FRAMEWORK/$COREFX_SHARED_FRAMEWORK_NAME/$TARGET_COREFX_VERSION/
--- /dev/null
+version: '3'
+services:
+ client:
+ image: httpstress
+ links:
+ - server
+ environment:
+ - HTTPSTRESS_ARGS=-runMode client -serverUri https://server:5001 ${HTTPSTRESS_ARGS}
+ server:
+ image: httpstress
+ ports:
+ - "5001:5001"
+ environment:
+ - HTTPSTRESS_ARGS=-runMode server -serverUri https://+:5001