Microservice.ps1
param( [string[]] $ListenerPrefix = @('http://localhost:6161/') ) # If we're running in a container and the listener prefix is not http://*:80/, if ($env:IN_CONTAINER -and $listenerPrefix -ne 'http://*:80/') { # then set the listener prefix to http://*:80/ (listen to all incoming requests on port 80). $listenerPrefix = 'http://*:80/' } # If we do not have a global DiceHttpListener object, if (-not $global:DiceHttpListener) { # then create a new HttpListener object. $global:DiceHttpListener = [Net.HttpListener]::new() # and add the listener prefixes. foreach ($prefix in $ListenerPrefix) { if ($global:DiceHttpListener.Prefixes -notcontains $prefix) { $global:DiceHttpListener.Prefixes.Add($prefix) } } } # The DiceServerJob will start the HttpListener and listen for incoming requests. $script:DiceServerJob = Start-ThreadJob -Name DiceServer -ScriptBlock { param([Net.HttpListener]$Listener) # Start the listener. try { $Listener.Start() } # If the listener cannot start, write a warning and return. catch { Write-Warning "Could not start listener: $_" ;return } # While the listener is running, while ($true) { # get the context of the incoming request. $listener.GetContextAsync() | . { process { # by enumerating the result, we effectively 'await' the result $context = $(try { $_.Result } catch { $_ }) # and can just return a context object $context } } } } -ArgumentList $global:DiceHttpListener # If PowerShell is exiting, close the HttpListener. Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action { $global:DiceHttpListener.Close() } # Keep track of the creation time of the DiceServerJob. $DiceServerJob | Add-Member -MemberType NoteProperty Created -Value ([DateTime]::now) -Force # Jobs have .Output, which in turn has a .DataAdded event. # this allows us to have an event-driven webserver. $subscriber = Register-ObjectEvent -InputObject $DiceServerJob.Output -EventName DataAdded -Action { $context = $event.Sender[$event.SourceEventArgs.Index] # When a context is added to the output, create a new event with the context and the time. New-Event -SourceIdentifier HTTP.Request -MessageData ($event.MessageData + [Ordered]@{ Context = $context Time = [DateTime]::Now }) } -SupportEvent -MessageData ([Ordered]@{Job = $DiceServerJob}) # Add the subscriber to the DiceServerJob (just in case). $DiceServerJob | Add-Member -MemberType NoteProperty OutputSubscriber -Value $subscriber -Force # Our custom 'HTTP.Request' event will process the incoming requests. Register-EngineEvent -SourceIdentifier HTTP.Request -Action { $context = $event.MessageData.Context # Get the request and response objects from the context. $request, $response = $context.Request, $context.Response # Do everything from here on in a try/catch block, so errors don't hurt the server. try { # Forget favicons. if ($request.Url.LocalPath -eq '/favicon.ico') { $response.StatusCode = 404 $response.Close() return } # If the request is for the root, return "No Dice". $outputMessage = if ($request.Url.LocalPath -eq '/') { "No Dice" } else { # Otherwise, get the segments of the URL. $strippedSegments = $request.Url.Segments -replace '/$' -ne '' # If the segments are integers, get the integer segments. $intSegments = @(foreach ($segment in $strippedSegments) { if ($segment -as [int]) { $segment -as [int] } }) -as [int[]] # If there is only one segment, roll a dice with that many sides. if ($intSegments.Count -eq 1) { Read-Dice -Sides $intSegments[0] | Format-Custom | Out-String } elseif ($intSegments -ge 2) { # If there are two segments, roll a dice with the first segment as the number of sides and the second segment as the number of rolls. Read-Dice -Sides $intSegments[0] -RollCount $intSegments[1] | Format-Custom | Out-String } else { # Otherwise, return "No Dice". "No Dice" } } # At this point our output message is a string, so we can convert it to bytes. $outputBuffer = $OutputEncoding.GetBytes($outputMessage) # and write the output buffer to the response stream. $response.OutputStream.Write($outputBuffer, 0, $outputBuffer.Length) # and we're done. $response.Close() } catch { # If anything goes wrong, write a warning # (this will be written to the console, making it easier for an admin to see what went wrong). Write-Warning "Error processing request: $_" } } # Write a message to the console that the dice server has started. @{"Message" = "Dice server started on $listenerPrefix @ $([datetime]::now.ToString('o'))"} | ConvertTo-Json | Out-Host # Wait for the PowerShell.Exiting event. while ($true) { $exiting = Wait-Event -SourceIdentifier PowerShell.Exiting -Timeout (Get-Random -Minimum 1 -Maximum 5) if ($exiting) { # If the DiceServerJob is still running, stop it. $DiceServerJob | Stop-Job # and break out of the loop. break } } |