Microservice.ps1
param( [string[]] $ListenerPrefix = @('http://localhost:8279/') ) # 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 RoughDraftHttpListener object, if (-not $global:RoughDraftHttpListener) { # then create a new HttpListener object. $global:RoughDraftHttpListener = [Net.HttpListener]::new() # and add the listener prefixes. foreach ($prefix in $ListenerPrefix) { if ($global:RoughDraftHttpListener.Prefixes -notcontains $prefix) { $global:RoughDraftHttpListener.Prefixes.Add($prefix) } } } # The RoughDraftServerJob will start the HttpListener and listen for incoming requests. $script:RoughDraftServerJob = Start-ThreadJob -Name RoughDraftServer -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:RoughDraftHttpListener # If PowerShell is exiting, close the HttpListener. Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action { $global:RoughDraftHttpListener.Close() } # Keep track of the creation time of the RoughDraftServerJob. $RoughDraftServerJob | 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 $RoughDraftServerJob.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 = $RoughDraftServerJob}) # Add the subscriber to the RoughDraftServerJob (just in case). $RoughDraftServerJob | 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 } $outputMessage = if ($RoughDraft.Serve.ScriptBlock) { # If the RoughDraft.Serve function exists, call it with the request and response objects. & $RoughDraft.Serve -Request $request } else { "Nothing to Serve" } if ($outputMessage.ContentType) { # If the output message has a ContentType, set the response content type. $response.ContentType = $outputMessage.ContentType } $headerHashtableKeys = 'Headers', 'Header' foreach ($key in $headerHashtableKeys) { if ($outputMessage.$key -is [Collections.IDictionary]) { # If the output message has a Headers or Header key, set the response headers. foreach ($headerKey in $outputMessage[$key].Keys) { $response.Headers.Add($headerKey, $outputMessage[$key][$headerKey]) } } } $outputBuffer = if ($outputMessage -is [string]) { # At this point our output message is a string, so we can convert it to bytes. $OutputEncoding.GetBytes($outputMessage) } elseif ($outputMessage -is [Collections.IDictionary]) { } elseif ($( $outputMessageBytes = $outputMessage -as [byte[]] $outputMessageBytes )) { $outputMessageBytes } if ($outputBuffer) { # 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" = "RoughDraft server started on $listenerPrefix @ $([datetime]::now.ToString('o'))"} | ConvertTo-Json | Out-Host |