-
-
Notifications
You must be signed in to change notification settings - Fork 8
/
Http.Server.Start.ps1
148 lines (125 loc) · 6.76 KB
/
Http.Server.Start.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<#
.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