Skip to content

Commit 477898c

Browse files
author
James Brundage
committed
feat: Http.Server.Start ( Fixes #865 )
1 parent b191871 commit 477898c

File tree

1 file changed

+148
-0
lines changed

1 file changed

+148
-0
lines changed

Http.Server.Start.ps1

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<#
2+
.SYNOPSIS
3+
Starts PipeScript as a HTTP Server.
4+
.DESCRIPTION
5+
Starts PipeScript as a HTTP server inside of a background job, then waits forever.
6+
.NOTES
7+
There are many ways you can route requests with PipeScript and PSNode.
8+
9+
This is a functional example of many of them at play.
10+
#>
11+
param()
12+
Push-Location $PSScriptRoot
13+
$ImportedModules = Get-ChildItem -Path /Modules -Directory |
14+
ForEach-Object { Import-Module $_.FullName -Global -Force -PassThru }
15+
16+
17+
# Requests can be routed by piping into PSNode, which will start multiple PSNode jobs.
18+
# Note that only one request can exist for a given host name, port, and path.
19+
& {
20+
[PSCustomObject]@{
21+
# So if we bind to every request coming in from every server, we can only have one job.
22+
Route = "http://*:80/"
23+
24+
Command = {
25+
$Url = $request.Url
26+
27+
28+
29+
# Of course, a job can have modules loaded, and
30+
$NowServingModule = $null
31+
# all loaded modules can have one or more .Server(s) associated with them.
32+
# If there is a server for a module, we want to defer to that.
33+
:FindingTheModuleForThisServer foreach ($loadedModule in Get-Module) {
34+
if ($loadedModule.Server) {
35+
foreach ($loadedModuleServer in $loadedModule.Server) {
36+
if ($loadedModuleServer -isnot [string]) {continue }
37+
if ($request.Url.Host -like $loadedModuleServer) {
38+
$NowServingModule = $loadedModule
39+
break FindingTheModuleForThisServer
40+
}
41+
}
42+
}
43+
}
44+
45+
if ($NowServingModule) {
46+
# If a module has a [ScriptBlock] value in it's server list, we can use this to respond
47+
# (manifests cannot declare a [ScriptBlock], it would have to be added during or after module load)
48+
foreach ($moduleServer in $NowServingModule) {
49+
if ($moduleServer -is [ScriptBlock]) {
50+
return . $moduleServer
51+
}
52+
}
53+
54+
# We can also serve up module responses using events.
55+
56+
# Each web request already basically is an event, we just need to broadcast it
57+
$NowServingMessage = ([PSCustomObject]([Ordered]@{RequestGUID=[GUID]::newguid().ToString()} + $psBoundParameters))
58+
$ServerEvent = New-Event -SourceIdentifier "$NowServingModule.$($request.URL.Scheme).Request" -Sender $NowServingModule -EventArguments $NowServingMessage -MessageData $NowServingMessage
59+
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).
60+
# Get all of the events
61+
$responseEvents = @(Get-Event -SourceIdentifier "$NowServingModule.$($request.URL.Scheme).Response.$($NowServingMessage.RequestGUID)" -ErrorAction Ignore)
62+
[Array]::Reverse($responseEvents) # and flip the order
63+
$responseEvent = $responseEvents[0] # so we get the most recent response.
64+
65+
# There are three forms of message data we'll consider a response:
66+
# * If the message data is now a string
67+
$NowServingResponse = $(if ($responseEvent.MessageData -is [string]) {
68+
$responseEvent.MessageData
69+
}
70+
# * If the message data has an .Answer
71+
elseif ($responseEvent.MessageData.Answer)
72+
{
73+
$responseEvent.MessageData.Answer
74+
}
75+
# * If the message data has a .Response.
76+
elseif ($responseEvent.MessageData.Response) {
77+
$responseEvent.MessageData.Response
78+
})
79+
# $ServerEvent
80+
$responseEvents | Remove-Event -ErrorAction Ignore # Remove the event to reclaim memory and keep things safe.
81+
82+
return $nowServingResponse # return whatever response we got (or nothing)
83+
}
84+
85+
86+
# Last but not least, we can do things the "old-fashioned" way and handle the request directly.
87+
88+
# We can easily use a switch statement to route by host
89+
:RouteHost switch ($Url.Host) {
90+
'pipescript.test' {
91+
"hello world" # Of course, this would get a little tedious and inflexible to do for each server
92+
}
93+
default {
94+
95+
# Another way we can route is by doing a regex based switch on the url. This can be _very_ flexible
96+
:RouteUrl switch -Regex ($url) {
97+
98+
default {
99+
# By default, we still want to look for any route commands we know about
100+
$pipescriptRoutes = Get-PipeScript -PipeScriptType Route
101+
102+
# If we have any routes,
103+
$mappedRoute = foreach ($psRoute in $pipescriptRoutes) {
104+
# we want to ensure they're valid, given this URL
105+
if (-not $psRoute.Validate($request.Url)) {
106+
continue
107+
}
108+
109+
# In this server, there can be only one valid route
110+
$psRoute
111+
break
112+
}
113+
114+
# If we've mapped to a single valid route
115+
if ($mappedRoute) {
116+
. $mappedRoute # run that
117+
} else {
118+
# Otherwise, show the PipeScript logo
119+
Get-Content (
120+
Get-Module PipeScript |
121+
Split-Path |
122+
Join-Path -ChildPath Assets |
123+
Join-Path -ChildPath PipeScript-4-chevron-animated.svg
124+
) -Raw
125+
}
126+
}
127+
}
128+
}
129+
}
130+
}
131+
ImportModule = $ImportedModules
132+
}
133+
} |
134+
Start-PSNode | Out-Host
135+
136+
# Now we enter an infinite loop where we let the jobs do their work
137+
while ($true) {
138+
$ev = $null
139+
$results = Get-Job | Receive-Job -errorVariable ev *>&1
140+
if ($ev) {
141+
$ev | Out-String
142+
break
143+
}
144+
145+
Start-Sleep -Seconds 5
146+
}
147+
148+
Pop-Location

0 commit comments

Comments
 (0)