Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Httpstress: Add checksum validation & support plaintext http #40360

Merged
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Text;

namespace HttpStress
{
// Adapted from https://github.com/dotnet/corefx/blob/41cd99d051102be4ed83f4f9105ae9e73aa48b7c/src/Common/tests/System/IO/Compression/CRC.cs
public static class CRC
{
// Table of CRCs of all 8-bit messages.
private static ulong[] s_crc_table = new ulong[256];
public const ulong InitialCrc = 0xffffffffL;

// Flag: has the table been computed? Initially false.
private static bool s_crc_table_computed = false;

// Make the table for a fast CRC.
// Derivative work of zlib -- https://github.com/madler/zlib/blob/master/crc32.c (hint: L108)
private static void make_crc_table()
{
ulong c;
int n, k;

for (n = 0; n < 256; n++)
{
c = (ulong)n;
for (k = 0; k < 8; k++)
{
if ((c & 1) > 0)
c = 0xedb88320L ^ (c >> 1);
else
c = c >> 1;
}
s_crc_table[n] = c;
}
s_crc_table_computed = true;
}

// Update a running CRC with the bytes buf[0..len-1]--the CRC
// should be initialized to all 1's, and the transmitted value
// is the 1's complement of the final running CRC (see the
// crc() routine below)).
public static ulong update_crc(ulong crc, byte[] buf, int len)
{
ulong c = crc;
int n;

if (!s_crc_table_computed)
make_crc_table();
for (n = 0; n < len; n++)
{
c = s_crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
}
return c;
}

public static ulong update_crc(ulong crc, string text, Encoding encoding = null)
{
encoding = encoding ?? Encoding.ASCII;
byte[] bytes = encoding.GetBytes(text);
return update_crc(crc, bytes, bytes.Length);
}

public static ulong CalculateCRC(byte[] buf) => update_crc(InitialCrc, buf, buf.Length) ^ InitialCrc;

public static ulong CalculateHeaderCrc<T>(IEnumerable<(string name, T)> headers, Encoding encoding = null) where T : IEnumerable<string>
{
ulong checksum = InitialCrc;

foreach ((string name, IEnumerable<string> values) in headers)
{
checksum = update_crc(checksum, name);
foreach (string value in values)
{
checksum = update_crc(checksum, value);
}
}

return checksum ^ InitialCrc;
}
}
}
160 changes: 112 additions & 48 deletions src/System.Net.Http/tests/StressTests/HttpStress/ClientOperations.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ public class Configuration
public bool ListOperations { get; set; }

public Version HttpVersion { get; set; }
public bool UseWinHttpHandler { get; set; }
public int ConcurrentRequests { get; set; }
public int RandomSeed { get; set; }
public int MaxContentLength { get; set; }
public int MaxRequestUriSize { get; set; }
public int MaxRequestHeaderCount { get; set; }
public int MaxRequestHeaderTotalSize { get; set; }
public int MaxParameters { get; set; }
public int[] OpIndices { get; set; }
public int[] ExcludedOpIndices { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<ItemGroup>
<PackageReference Include="System.CommandLine.Experimental" Version="0.3.0-alpha.19317.1" />
<PackageReference Include="System.Net.Http.WinHttpHandler" Version="4.5.4" />
</ItemGroup>

</Project>
12 changes: 9 additions & 3 deletions src/System.Net.Http/tests/StressTests/HttpStress/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ private static bool TryParseCli(string[] args, out Configuration config)
cmd.AddOption(new Option("-serverUri", "Stress suite server uri.") { Argument = new Argument<Uri>("serverUri", new Uri("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("-maxContentLength", "Max content length for request and response bodies.") { Argument = new Argument<int>("numBytes", 1000) });
cmd.AddOption(new Option("-maxRequestUriSize", "Max query string length support by the server.") { Argument = new Argument<int>("numChars", 8000) });
cmd.AddOption(new Option("-maxRequestUriSize", "Max query string length support by the server.") { Argument = new Argument<int>("numChars", 5000) });
cmd.AddOption(new Option("-maxRequestHeaderCount", "Maximum number of headers to place in request") { Argument = new Argument<int>("numHeaders", 90) });
cmd.AddOption(new Option("-maxRequestHeaderTotalSize", "Max request header total size.") { Argument = new Argument<int>("numBytes", 1000) });
cmd.AddOption(new Option("-http", "HTTP version (1.1 or 2.0)") { Argument = new Argument<Version>("version", HttpVersion.Version20) });
cmd.AddOption(new Option("-connectionLifetime", "Max connection lifetime length (milliseconds).") { Argument = new Argument<int?>("connectionLifetime", null) });
cmd.AddOption(new Option("-ops", "Indices of the operations to use") { Argument = new Argument<int[]>("space-delimited indices", null) });
Expand All @@ -47,6 +49,7 @@ private static bool TryParseCli(string[] args, out Configuration config)
cmd.AddOption(new Option("-numParameters", "Max number of query parameters or form fields for a request.") { Argument = new Argument<int>("queryParameters", 1) });
cmd.AddOption(new Option("-cancelRate", "Number between 0 and 1 indicating rate of client-side request cancellation attempts. Defaults to 0.1.") { Argument = new Argument<double>("probability", 0.1) });
cmd.AddOption(new Option("-httpSys", "Use http.sys instead of Kestrel.") { Argument = new Argument<bool>("enable", false) });
cmd.AddOption(new Option("-winHttp", "Use WinHttpHandler for the stress client.") { Argument = new Argument<bool>("enable", false) });
cmd.AddOption(new Option("-displayInterval", "Client stats display interval in seconds. Defaults to 5 seconds.") { Argument = new Argument<int>("seconds", 5) });
cmd.AddOption(new Option("-clientTimeout", "Default HttpClient timeout in seconds. Defaults to 10 seconds.") { Argument = new Argument<int>("seconds", 10) });

Expand All @@ -70,10 +73,13 @@ private static bool TryParseCli(string[] args, out Configuration config)
ListOperations = cmdline.ValueForOption<bool>("-listOps"),

HttpVersion = cmdline.ValueForOption<Version>("-http"),
UseWinHttpHandler = cmdline.ValueForOption<bool>("-winHttp"),
ConcurrentRequests = cmdline.ValueForOption<int>("-n"),
RandomSeed = cmdline.ValueForOption<int?>("-seed") ?? new Random().Next(),
MaxContentLength = cmdline.ValueForOption<int>("-maxContentLength"),
MaxRequestUriSize = cmdline.ValueForOption<int>("-maxRequestUriSize"),
MaxRequestHeaderCount = cmdline.ValueForOption<int>("-maxRequestHeaderCount"),
MaxRequestHeaderTotalSize = cmdline.ValueForOption<int>("-maxRequestHeaderTotalSize"),
OpIndices = cmdline.ValueForOption<int[]>("-ops"),
ExcludedOpIndices = cmdline.ValueForOption<int[]>("-xops"),
MaxParameters = cmdline.ValueForOption<int>("-numParameters"),
Expand Down Expand Up @@ -104,9 +110,9 @@ private static async Task Run(Configuration config)
return;
}

if (config.ServerUri.Scheme != "https")
if (!config.ServerUri.Scheme.StartsWith("http"))
{
Console.Error.WriteLine("Server uri must be https.");
Console.Error.WriteLine("Invalid server uri");
return;
}

Expand Down
69 changes: 47 additions & 22 deletions src/System.Net.Http/tests/StressTests/HttpStress/StressClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
Expand All @@ -19,6 +18,8 @@ namespace HttpStress
{
public class StressClient : IDisposable
{
private const string UNENCRYPTED_HTTP2_ENV_VAR = "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2UNENCRYPTEDSUPPORT";

private readonly (string name, Func<RequestContext, Task> operation)[] _clientOperations;
private readonly Configuration _config;
private readonly StressResultAggregator _aggregator;
Expand Down Expand Up @@ -77,16 +78,34 @@ public void PrintFinalReport()

private async Task StartCore()
{
var handler = new SocketsHttpHandler()
if (_config.ServerUri.Scheme == "http")
{
Environment.SetEnvironmentVariable(UNENCRYPTED_HTTP2_ENV_VAR, "1");
}

HttpMessageHandler CreateHttpHandler()
{
PooledConnectionLifetime = _config.ConnectionLifetime.GetValueOrDefault(Timeout.InfiniteTimeSpan),
SslOptions = new SslClientAuthenticationOptions
if (_config.UseWinHttpHandler)
{
RemoteCertificateValidationCallback = delegate { return true; }
return new System.Net.Http.WinHttpHandler()
{
ServerCertificateValidationCallback = delegate { return true; }
};
}
else
{
return new SocketsHttpHandler()
{
PooledConnectionLifetime = _config.ConnectionLifetime.GetValueOrDefault(Timeout.InfiniteTimeSpan),
SslOptions = new SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback = delegate { return true; }
}
};
}
};
}

using var client = new HttpClient(handler) { BaseAddress = _config.ServerUri, Timeout = _config.DefaultTimeout };
using var client = new HttpClient(CreateHttpHandler()) { BaseAddress = _config.ServerUri, Timeout = _config.DefaultTimeout };

async Task RunWorker(int taskNum)
{
Expand Down Expand Up @@ -148,15 +167,16 @@ private sealed class StressFailureType
{
// Representative error text of stress failure
public string ErrorText { get; }
public ImmutableDictionary<int, int> Failures { get; }
// Operation id => failure timestamps
public Dictionary<int, List<DateTime>> Failures { get; }

public StressFailureType(string errorText, ImmutableDictionary<int, int> failures)
public StressFailureType(string errorText)
{
ErrorText = errorText;
Failures = failures;
Failures = new Dictionary<int, List<DateTime>>();
}

public int FailureCount => Failures.Values.Sum();
public int FailureCount => Failures.Values.Select(x => x.Count).Sum();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public int FailureCount => Failures.Values.Select(x => x.Count).Sum();
public int FailureCount => Failures.Values.Sum(x => x.Count);

}

private sealed class StressResultAggregator
Expand Down Expand Up @@ -198,6 +218,8 @@ public void RecordCancellation(int operationIndex, TimeSpan elapsed)

public void RecordFailure(Exception exn, int operationIndex, TimeSpan elapsed, int taskNum, long iteration)
{
DateTime timestamp = DateTime.Now;

Interlocked.Increment(ref _totalRequests);
Interlocked.Increment(ref _failures[operationIndex]);

Expand All @@ -211,17 +233,19 @@ void RecordFailureType()
{
(Type, string, string)[] key = ClassifyFailure(exn);

_failureTypes.AddOrUpdate(key, Add, Update);
StressFailureType failureType = _failureTypes.GetOrAdd(key, _ => new StressFailureType(exn.ToString()));

StressFailureType Add<T>(T key)
lock (failureType)
{
return new StressFailureType(exn.ToString(), ImmutableDictionary<int, int>.Empty.SetItem(operationIndex, 1));
}
List<DateTime> timestamps;

StressFailureType Update<T>(T key, StressFailureType current)
{
current.Failures.TryGetValue(operationIndex, out int failureCount);
return new StressFailureType(current.ErrorText, current.Failures.SetItem(operationIndex, failureCount + 1));
if(!failureType.Failures.TryGetValue(operationIndex, out timestamps))
{
timestamps = new List<DateTime>();
failureType.Failures.Add(operationIndex, timestamps);
}

timestamps.Add(timestamp);
}

(Type exception, string message, string callSite)[] ClassifyFailure(Exception exn)
Expand Down Expand Up @@ -361,15 +385,16 @@ public void PrintFailureTypes()
Console.WriteLine(failure.ErrorText);
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Yellow;
foreach (KeyValuePair<int, int> operation in failure.Failures)
foreach (KeyValuePair<int, List<DateTime>> operation in failure.Failures)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write($"\t{_operationNames[operation.Key].PadRight(30)}");
Console.ResetColor();
Console.ForegroundColor = ConsoleColor.Red;
Console.Write($"Fail: ");
Console.Write("Fail: ");
Console.ResetColor();
Console.WriteLine(operation.Value);
Console.Write(operation.Value.Count);
Console.WriteLine($"\tTimestamps: {string.Join(", ", operation.Value.Select(x => x.ToString("HH:mm:ss")))}");
}

Console.ForegroundColor = ConsoleColor.Cyan;
Expand Down
Loading