static void SimulateLoad(int numUsers, int numInteractions,
                                 IInteractionMonitor monitor)
        {
            Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US", false);

            var watch = new Stopwatch();
            var rnd   = new Random(123); // Constant seed for repeatable experiments

            Console.Write($"Creating {numUsers:n0} users...\t\t");

            watch.Start();
            for (var i = 0; i < numUsers; i++)
            {
                monitor.RegisterUser(i);
            }
            Console.WriteLine($"done in {watch.Elapsed.TotalSeconds:0.000}");

            Console.Write($"Performing {numInteractions:n0} interactions...\t");
            watch.Restart();
            for (var i = 0; i < numInteractions; i++)
            {
                var user1 = rnd.Next(numUsers);
                var user2 = rnd.Next(numUsers);
                monitor.RegisterInteraction(user1, user2);
            }
            Console.WriteLine($"done in {watch.Elapsed.TotalSeconds:0.000}");

            Console.Write($"Getting all interaction groups...\t");
            watch.Restart();
            var allGroups = monitor.GetAllInteractionGroups();

            Console.WriteLine($"done in {watch.Elapsed.TotalSeconds:0.000}");
            Console.WriteLine("Number of groups: {0:n0}", allGroups.Count());
        }
        //private static readonly
        private AspNetCoreServirtiumServer(IHostBuilder hostBuilder, IInteractionMonitor monitor, IInteractionTransforms interactionTransforms, int?port)
        {
            _interactionMonitor = monitor;
            _host = hostBuilder.ConfigureWebHostDefaults(webBuilder =>
            {
                if (port != null)
                {
                    //If a port is specified, override urls with specified port, listening on all available hosts, for HTTP.
                    webBuilder.UseUrls($"http://*:{port}");
                }
                webBuilder.Configure(app =>
                {
                    app.Run(async ctx =>
                    {
                        var targetHost         = new Uri($"{ctx.Request.Scheme}{Uri.SchemeDelimiter}{ctx.Request.Host}");
                        var requestInteraction = new MarkdownInteraction.Builder()
                                                 .Number(_interactionCounter.Bump())
                                                 .Method(new System.Net.Http.HttpMethod(ctx.Request.Method))
                                                 .Path($"{ctx.Request.Path}{ctx.Request.QueryString}")
                                                 //Remap headers from a dictionary of string lists to a list of (string, string) tuples
                                                 .RequestHeaders
                                                 (
                            ctx.Request.Headers
                            .SelectMany(kvp => kvp.Value.Select(val => (kvp.Key, val)))
                            .ToArray()
                                                 )
                                                 .Build();
                        var serviceRequestInteraction = interactionTransforms.TransformClientRequestForRealService(requestInteraction);
                        var responseFromService       = await monitor.GetServiceResponseForRequest(
                            targetHost,
                            serviceRequestInteraction,
                            false);
                        var clientResponse = interactionTransforms.TransformRealServiceResponseForClient(responseFromService);

                        //Always remove the 'Transfer-Encoding: chunked' header if present.
                        //If it's present in the response.Headers collection ast this point, Kestrel expects you to add chunk notation to the body yourself
                        //However if you just send it with no content-length, Kestrel will add the chunked header and chunk the body for you.
                        clientResponse = clientResponse
                                         .WithRevisedHeaders(
                            clientResponse.Headers
                            .Where((h) => !(h.Name.ToLower() == "transfer-encoding" && h.Value.ToLower() == "chunked")))
                                         .WithReadjustedContentLength();
                        ctx.Response.OnCompleted(() =>
                        {
                            Console.WriteLine($"{requestInteraction.Method} Request to {targetHost}{requestInteraction.Path} returned to client with code {ctx.Response.StatusCode}");
                            return(Task.CompletedTask);
                        });

                        //Transfer adjusted headers to the response going out to the client
                        foreach ((string headerName, string headerValue) in clientResponse.Headers)
                        {
                            if (ctx.Response.Headers.TryGetValue(headerName, out var headerInResponse))
                            {
                                ctx.Response.Headers[headerName] = new StringValues(headerInResponse.Append(headerValue).ToArray());
                            }
                            else
                            {
                                ctx.Response.Headers[headerName] = new StringValues(headerValue);
                            }
                        }
                        if (clientResponse.Body != null)
                        {
                            await ctx.Response.WriteAsync(clientResponse.Body.ToString());
                        }
                        await ctx.Response.CompleteAsync();
                    });
                });
            }).Build();
        }
 public static AspNetCoreServirtiumServer WithCommandLineArgs(string[] args, IInteractionMonitor monitor, IInteractionTransforms interactionTransforms) =>
 new AspNetCoreServirtiumServer(Host.CreateDefaultBuilder(args), monitor, interactionTransforms, null);
 public static AspNetCoreServirtiumServer Default(int port, IInteractionMonitor monitor, Uri serviceHost) =>
 new AspNetCoreServirtiumServer(Host.CreateDefaultBuilder(), monitor, new SimpleInteractionTransforms(serviceHost), port);
 public static AspNetCoreServirtiumServer WithTransforms(int port, IInteractionMonitor monitor, IInteractionTransforms interactionTransforms) =>
 new AspNetCoreServirtiumServer(Host.CreateDefaultBuilder(), monitor, interactionTransforms, port);