Http.Server.Start.ps1

<#
.SYNOPSIS
    Starts PipeScript as a HTTP Server.
.DESCRIPTION
    Starts PipeScript as a HTTP server inside of a background job, then waits forever.
.NOTES
    There are many ways you can route requests with PipeScript and PSNode.

    This is a functional example of many of them at play.
#>

param()
Push-Location $PSScriptRoot
$ImportedModules = Get-ChildItem -Path /Modules -Directory |
    ForEach-Object { Import-Module $_.FullName -Global -Force -PassThru }


# Requests can be routed by piping into PSNode, which will start multiple PSNode jobs.
# Note that only one request can exist for a given host name, port, and path.
& {
    [PSCustomObject]@{
        # So if we bind to every request coming in from every server, we can only have one job.
        Route = "http://*:80/" 
        
        Command = {
            $Url = $request.Url



            # Of course, a job can have modules loaded, and
            $NowServingModule = $null
            # all loaded modules can have one or more .Server(s) associated with them.
            # If there is a server for a module, we want to defer to that.
            :FindingTheModuleForThisServer foreach ($loadedModule in Get-Module) {
                if ($loadedModule.Server) {
                    foreach ($loadedModuleServer in $loadedModule.Server) {
                        if ($loadedModuleServer -isnot [string]) {continue }
                        if ($request.Url.Host -like $loadedModuleServer) {
                            $NowServingModule = $loadedModule
                            break FindingTheModuleForThisServer
                        }
                    }
                }
            }

            if ($NowServingModule) {
                # If a module has a [ScriptBlock] value in it's server list, we can use this to respond
                # (manifests cannot declare a [ScriptBlock], it would have to be added during or after module load)
                foreach ($moduleServer in $NowServingModule) {                    
                    if ($moduleServer -is [ScriptBlock]) {
                        return . $moduleServer
                    }
                }

                # We can also serve up module responses using events.
                
                # Each web request already basically is an event, we just need to broadcast it
                $NowServingMessage = ([PSCustomObject]([Ordered]@{RequestGUID=[GUID]::newguid().ToString()} + $psBoundParameters))                
                $ServerEvent   = New-Event -SourceIdentifier "$NowServingModule.$($request.URL.Scheme).Request" -Sender $NowServingModule -EventArguments $NowServingMessage -MessageData $NowServingMessage
                Start-Sleep -Milliseconds 1 # and then wait a literal millisecond so the server can respond (the response can take more than a millisecond, the timeout cannot).
                # Get all of the events
                $responseEvents = @(Get-Event -SourceIdentifier "$NowServingModule.$($request.URL.Scheme).Response.$($NowServingMessage.RequestGUID)" -ErrorAction Ignore)
                [Array]::Reverse($responseEvents) # and flip the order
                $responseEvent = $responseEvents[0] # so we get the most recent response.

                # There are three forms of message data we'll consider a response:
                # * If the message data is now a string
                $NowServingResponse = $(if ($responseEvent.MessageData -is [string]) {
                    $responseEvent.MessageData
                }
                # * If the message data has an .Answer
                elseif ($responseEvent.MessageData.Answer) 
                {
                    $responseEvent.MessageData.Answer
                } 
                # * If the message data has a .Response.
                elseif ($responseEvent.MessageData.Response) {
                    $responseEvent.MessageData.Response
                })
                # $ServerEvent
                $responseEvents | Remove-Event -ErrorAction Ignore # Remove the event to reclaim memory and keep things safe.
                
                return $nowServingResponse # return whatever response we got (or nothing)
            }


            # Last but not least, we can do things the "old-fashioned" way and handle the request directly.
            
            # We can easily use a switch statement to route by host
            :RouteHost switch ($Url.Host) {
                'pipescript.test' {
                    "hello world" # Of course, this would get a little tedious and inflexible to do for each server
                }                 
                default {                    

                    # Another way we can route is by doing a regex based switch on the url. This can be _very_ flexible
                    :RouteUrl switch -Regex ($url) {
                        
                        default {
                            # By default, we still want to look for any route commands we know about
                            $pipescriptRoutes = Get-PipeScript -PipeScriptType Route
                            
                            # If we have any routes,
                            $mappedRoute = foreach ($psRoute in $pipescriptRoutes) {
                                # we want to ensure they're valid, given this URL
                                if (-not $psRoute.Validate($request.Url)) {
                                    continue
                                }
                            
                                # In this server, there can be only one valid route
                                $psRoute
                                break
                            }  
                                               
                            # If we've mapped to a single valid route
                            if ($mappedRoute) {
                                . $mappedRoute # run that
                            } else {
                                # Otherwise, show the PipeScript logo
                                Get-Content (
                                    Get-Module PipeScript | 
                                    Split-Path | 
                                    Join-Path -ChildPath Assets | 
                                    Join-Path -ChildPath PipeScript-ouroborus-animated.svg
                                ) -Raw
                            }
                        }
                    }
                }
            }                         
        }
        ImportModule = $ImportedModules
    }
} | 
    Start-PSNode | Out-Host

# Now we enter an infinite loop where we let the jobs do their work
while ($true) {
    $ev = $null
    $results = Get-Job | Receive-Job -errorVariable ev *>&1
    if ($ev) {
        $ev | Out-String
        break
    }
    
    Start-Sleep -Seconds 5
}

Pop-Location