Servers/MathServer.ps1

<#
.SYNOPSIS
    Math Server
.DESCRIPTION
    A simple math server.

    Each first request segment must map to a static method on the `[Math]` class
.EXAMPLE
    ./MathServer.ps1
#>

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)/"
)

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

$io = [Ordered]@{ # Pack our job input into an IO dictionary
    HttpListener = $httpListener 
}

# Our server is a thread job
Start-ThreadJob -ScriptBlock {param([Collections.IDictionary]$io)
    $psvariable = $ExecutionContext.SessionState.PSVariable
    foreach ($key in $io.Keys) { # First, let's unpack.
        if ($io[$key] -is [PSVariable]) { $psvariable.set($io[$key]) }
        else { $psvariable.set($key, $io[$key]) }
    }
    $staticMembers = [Math] | Get-Member -Static 
    # Listen for the next request
    :nextRequest while ($httpListener.IsListening) {     
        $getContext = $httpListener.GetContextAsync()
        while (-not $getContext.Wait(17)) { }
        $request, $reply =
            $getContext.Result.Request, $getContext.Result.Response
        
        $segments = @($request.Url.Segments)
        if ($segments.Length -le 1) {            
            $reply.ContentType = 'text/html'
            $memberList = @(
                "<ul>"
                foreach ($staticMember in $staticMembers) {
                    "<li>"
                    "<a href='/$($staticMember.name)'>$(
                        $staticMember.name
                    )</a>"

                    "</li>"
                }
                "</ul>"
            ) -join [Environment]::NewLine
            $reply.Close($OutputEncoding.GetBytes($memberList), $false)                        
            continue nextRequest
        } else {
            $firstSegment = $segments[1] -replace '/'
            $mathMember = [Math]::$firstSegment
            if ($null -eq $mathMember) {
                $reply.StatusCode = 404                
                $reply.Close()
                continue nextRequest
            }
        }

        if ($mathMember.Invoke) {
            $mathArgs = 
                if ($segments.Length -ge 3) {
                    @(for ($segmentNumber = 2; $segmentNumber -lt $segments.Length; $segmentNumber++) {
                        $segments[$segmentNumber] -replace '/' -as [double]
                    })
                } else {
                    $Reply.Close($OutputEncoding.GetBytes("$(
                        $mathMember.OverloadDefinitions -join [Environment]::NewLine
                    )"
), $false)    
                }            
            try {
                $result = $mathMember.Invoke($mathArgs)
                $Reply.Close($OutputEncoding.GetBytes("$result"), $false)
            } catch {
                $Reply.Close($OutputEncoding.GetBytes("$($_ | Out-String)"), $false)
            }
        } else {
            $Reply.Close($OutputEncoding.GetBytes("$mathMember"), $false)
        }
    }            
} -ThrottleLimit 100 -ArgumentList $IO -Name "$RootUrl" | # Output our job,
    Add-Member -NotePropertyMembers @{ # but attach a few properties first:
        HttpListener=$httpListener # * The listener (so we can stop it)
        IO=$IO # * The IO (so we can change it)
        Url="$RootUrl" # The URL (so we can easily access it).
    } -Force -PassThru # Pass all of that thru and return it to you.