1+ <#
2+ . SYNOPSIS
3+ Event Server
4+ . DESCRIPTION
5+ A simple event driven server.
6+
7+ Each request will generate an event, which will be responded to by a handler.
8+ . EXAMPLE
9+ ./EventServer.ps1 ($pwd | Split-Path)
10+ #>
11+ param (
12+ # The rootDirectory.
13+ [string ]$RootDirectory = $PSScriptRoot ,
14+
15+ # The rootUrl of the server. By default, a random loopback address.
16+ [string ]$RootUrl =
17+ " http://127.0.0.1:$ ( Get-Random - Minimum 4200 - Maximum 42000 ) /" ,
18+
19+ # The type map. This determines how each extension will be served.
20+ [Collections.IDictionary ]
21+ $TypeMap = [Ordered ]@ {
22+ " .html" = " text/html" ; " .css" = " text/css" ; " .svg" = " image/svg+xml" ;
23+ " .png" = " image/png" ; " .jpg" = " image/jpeg" ; " .gif" = " image/gif"
24+ " .mp3" = " audio/mpeg" ; " .mp4" = " video/mp4"
25+ " .json" = " application/json" ; " .xml" = " application/xml" ;
26+ " .js" = " text/javascript" ; " .jsm" = " text/javascript" ;
27+ }
28+ )
29+
30+ $httpListener = [Net.HttpListener ]::new()
31+ $httpListener.Prefixes.Add ($RootUrl )
32+ Write-Warning " Listening on $RootUrl $ ( $httpListener.Start ()) "
33+
34+ $io = [Ordered ]@ { # Pack our job input into an IO dictionary
35+ HttpListener = $httpListener ; ServerRoot = $RootDirectory
36+ MainRunspace = [Runspace ]::DefaultRunspace; SourceIdentifier = $RootUrl
37+ TypeMap = $TypeMap
38+ }
39+
40+ # Our server is a thread job
41+ Start-ThreadJob - ScriptBlock {param ([Collections.IDictionary ]$io )
42+ $psvariable = $ExecutionContext.SessionState.PSVariable
43+ foreach ($key in $io.Keys ) { # First, let's unpack.
44+ if ($io [$key ] -is [PSVariable ]) { $psvariable.set ($io [$key ]) }
45+ else { $psvariable.set ($key , $io [$key ]) }
46+ }
47+
48+ # Listen for the next request
49+ :nextRequest while ($httpListener.IsListening ) {
50+ $getContext = $httpListener.GetContextAsync ()
51+ while (-not $getContext.Wait (17 )) { }
52+ $request , $reply =
53+ $getContext.Result.Request , $getContext.Result.Response
54+ # Generate an event for every request
55+ $mainRunspace.Events.GenerateEvent (
56+ $SourceIdentifier , $httpListener , @ (
57+ $getContext.Result , $request , $reply
58+ ), [Ordered ]@ {
59+ Method = $Request.HttpMethod ; Url = $request.Url
60+ Request = $request ; Reply = $reply ; Response = $reply
61+ ServerRoot = $ServerRoot ; TypeMap = $TypeMap
62+ }
63+ )
64+ }
65+ } - ThrottleLimit 100 - ArgumentList $IO - Name " $RootUrl " | # Output our job,
66+ Add-Member - NotePropertyMembers @ { # but attach a few properties first:
67+ HttpListener = $httpListener # * The listener (so we can stop it)
68+ IO = $IO # * The IO (so we can change it)
69+ Url = " $RootUrl " # The URL (so we can easily access it).
70+ } - Force - PassThru # Pass all of that thru and return it to you.
71+
72+ # Now register a handler for these events.
73+ Register-EngineEvent - SourceIdentifier $RootUrl - Action {
74+ $request = $event.MessageData.Request
75+ $reply = $event.MessageData.Reply
76+
77+ $timeToRespond = [DateTime ]::Now - $event.TimeGenerated
78+ $myReply = " $ ( $request.HttpMethod ) $ ( $request.Url ) $ ( $timeToRespond ) "
79+ $reply.Close ($OutputEncoding.GetBytes ($myReply ), $false )
80+ }
81+
82+ # Because events are processed on the main runspace thread, this cannot Invoke-RestMethod itself.
0 commit comments