Servers/DebugServer.ps1

<#
.SYNOPSIS
    A debug server.
.DESCRIPTION
    A server that runs on the current thread, so you can debug it.

    You can run this with -AsJob, but then you cannot debug in PowerShell.
.NOTES
    A few notes:

    1. This will effectively lock the current thread (CTRL+C works).
    2. Because of the way requests are processed, you may need to refresh to hit the breakpoint.
    3. Be aware that browsers will request a `favicon.ico` first.
#>

param(
# The rootUrl of the server. By default, a random loopback address.
[string]$RootUrl=
    "http://127.0.0.1:$(Get-Random -Minimum 4200 -Maximum 42000)/",

# If set, will run in a background job.
[switch]
$AsJob
)

$httpListener = [Net.HttpListener]::new()
$httpListener.Prefixes.add($RootUrl)
$httpListener.Start()
Write-Warning "Listening on $rootUrl"

$listenScript = {
    param([Net.HttpListener]$httpListener)
    # Listen for the next request
    :nextRequest while ($httpListener.IsListening) {     
        $getContext = $httpListener.GetContextAsync()
        
        while (-not $getContext.Wait(17)) { }            

        $context = $getContext.Result
        $requestTime = [DateTime]::Now
        $request, $reply = $context.Request, $context.Response
        $debugObject = $request |
            Select-Object HttpMethod, Url, Is* |
            Add-Member NoteProperty Headers ([Ordered]@{}) -Force  -passThru |
            Add-Member NoteProperty Query ([Ordered]@{}) -Force  -passThru

        foreach ($headerName in $request.Headers) {
            $debugObject.headers[$headerName] = $request.Headers[$headerName]
        }
        if ($request.Url.Query) {            
            
            foreach ($chunk in $request.Url.Query -split '&') {
                $parsedQuery =
                    [Web.HttpUtility]::ParseQueryString($chunk)
                $key = @($parsedQuery.Keys)[0]
                if ($debugObject.Query[$key]) {
                    $debugObject.Query[$key] = @(
                        $debugObject.Query[$key]
                    ) + $parsedQuery[$key]
                } else {
                    $debugObject.Query[$key] = $parsedQuery[$key]
                } 
            }
        }
        $reply.ContentType = 'application/json'
        $reply.Close(        
            $OutputEncoding.GetBytes(    
                ($debugObject |  ConvertTo-Json -Depth 5)
                    ), $false)
        "Responded to $($Request.Url) in $([DateTime]::Now - $requestTime)"
    }
}

if ($AsJob) {
    Start-ThreadJob -ScriptBlock $listenerScript -ArgumentList $httpListener
} else {
    . $listenScript $httpListener
}