Skip to content

Commit 668bc4b

Browse files
Merge pull request #20 from PoshWeb/servers103
Servers101 0.1.2
2 parents 46cd4c4 + f8b0bb2 commit 668bc4b

6 files changed

Lines changed: 228 additions & 47 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## Servers101 0.1.2:
2+
3+
* Added Error Code Support to Server101 (#18)
4+
* MathServer (#15)
5+
* README installation and streaming server instructions (#19)
6+
7+
---
8+
9+
110
## Servers101 0.1.1:
211

312
* New Servers:

README.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,30 @@ This is a collection of simple servers in PowerShell.
1818

1919
Feel free to [contribute](contributing.md) and add your own.
2020

21-
22-
## Server Samples
21+
## Sample Servers
2322

2423
* [DebugServer.ps1](/Servers/DebugServer.ps1)
2524
* [DualEventServer.ps1](/Servers/DualEventServer.ps1)
2625
* [EventServer.ps1](/Servers/EventServer.ps1)
26+
* [MathServer.ps1](/Servers/MathServer.ps1)
2727
* [MethodSwitchServer.ps1](/Servers/MethodSwitchServer.ps1)
2828
* [Server101.ps1](/Servers/Server101.ps1)
2929
* [SwitchRegexServer.ps1](/Servers/SwitchRegexServer.ps1)
3030

31+
## Installing
32+
33+
You can install Servers101 from the [PowerShell Gallery](https://powershellgallery.com)
34+
35+
~~~PowerShell
36+
Install-Module Servers101
37+
~~~
38+
39+
Once installed, you can import it:
40+
41+
~~~PowerShell
42+
Import-Module Servers101 -PassThru
43+
~~~
44+
3145
## Using this module
3246

3347
This module has only one command, Get-Servers101.
@@ -39,3 +53,24 @@ Each server will be self-contained in a single script.
3953
To start the server, simply run the script.
4054

4155
To learn about how each server works, read thru each script.
56+
57+
## Streaming Server101
58+
59+
Because each server is contained within a single file, the servers can be streamed to a file
60+
61+
For example, to start a local file server, we can run:
62+
63+
~~~PowerShell
64+
Invoke-RestMethod https://cdn.jsdelivr.net/gh/PoshWeb/Servers101@latest/Servers/Server101.ps1 > ./server.ps1;
65+
./server.ps1
66+
~~~
67+
68+
For some servers it is also possible to run with Invoke-Expression.
69+
70+
You should never Invoke-Expression code you cannot trust and verify.
71+
72+
To stream a local file server, we can run:
73+
74+
~~~PowerShell
75+
irm https://cdn.jsdelivr.net/gh/PoshWeb/Servers101@latest/Servers/Server101.ps1 | iex
76+
~~~

README.md.ps1

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ This is a collection of simple servers in PowerShell.
1919
2020
Feel free to [contribute](contributing.md) and add your own.
2121
22-
23-
## Server Samples
22+
## Sample Servers
2423
2524
"@
2625

@@ -30,9 +29,22 @@ foreach ($serverScript in Get-Servers101) {
3029
}
3130

3231

33-
3432
@"
3533
34+
## Installing
35+
36+
You can install Servers101 from the [PowerShell Gallery](https://powershellgallery.com)
37+
38+
~~~PowerShell
39+
Install-Module Servers101
40+
~~~
41+
42+
Once installed, you can import it:
43+
44+
~~~PowerShell
45+
Import-Module Servers101 -PassThru
46+
~~~
47+
3648
## Using this module
3749
3850
This module has only one command, `Get-Servers101`.
@@ -44,4 +56,29 @@ Each server will be self-contained in a single script.
4456
To start the server, simply run the script.
4557
4658
To learn about how each server works, read thru each script.
59+
"@
60+
61+
62+
@"
63+
64+
## Streaming Server101
65+
66+
Because each server is contained within a single file, the servers can be streamed to a file
67+
68+
For example, to start a local file server, we can run:
69+
70+
~~~PowerShell
71+
Invoke-RestMethod https://cdn.jsdelivr.net/gh/PoshWeb/Servers101@latest/Servers/Server101.ps1 > ./server.ps1;
72+
./server.ps1
73+
~~~
74+
75+
For some servers it is also possible to run with Invoke-Expression.
76+
77+
You should never Invoke-Expression code you cannot trust and verify.
78+
79+
To stream a local file server, we can run:
80+
81+
~~~PowerShell
82+
irm https://cdn.jsdelivr.net/gh/PoshWeb/Servers101@latest/Servers/Server101.ps1 | iex
83+
~~~
4784
"@

Servers/MathServer.ps1

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<#
2+
.SYNOPSIS
3+
Math Server
4+
.DESCRIPTION
5+
A simple math server.
6+
7+
Each first request segment must map to a static method on the `[Math]` class
8+
.EXAMPLE
9+
./MathServer.ps1
10+
#>
11+
param(
12+
# The rootUrl of the server. By default, a random loopback address.
13+
[string]$RootUrl=
14+
"http://127.0.0.1:$(Get-Random -Minimum 4200 -Maximum 42000)/"
15+
)
16+
17+
$httpListener = [Net.HttpListener]::new()
18+
$httpListener.Prefixes.Add($RootUrl)
19+
Write-Warning "Listening on $RootUrl $($httpListener.Start())"
20+
21+
$io = [Ordered]@{ # Pack our job input into an IO dictionary
22+
HttpListener = $httpListener
23+
}
24+
25+
# Our server is a thread job
26+
Start-ThreadJob -ScriptBlock {param([Collections.IDictionary]$io)
27+
$psvariable = $ExecutionContext.SessionState.PSVariable
28+
foreach ($key in $io.Keys) { # First, let's unpack.
29+
if ($io[$key] -is [PSVariable]) { $psvariable.set($io[$key]) }
30+
else { $psvariable.set($key, $io[$key]) }
31+
}
32+
$staticMembers = [Math] | Get-Member -Static
33+
# Listen for the next request
34+
:nextRequest while ($httpListener.IsListening) {
35+
$getContext = $httpListener.GetContextAsync()
36+
while (-not $getContext.Wait(17)) { }
37+
$request, $reply =
38+
$getContext.Result.Request, $getContext.Result.Response
39+
40+
$segments = @($request.Url.Segments)
41+
if ($segments.Length -le 1) {
42+
$reply.ContentType = 'text/html'
43+
$memberList = @(
44+
"<ul>"
45+
foreach ($staticMember in $staticMembers) {
46+
"<li>"
47+
"<a href='/$($staticMember.name)'>$(
48+
$staticMember.name
49+
)</a>"
50+
"</li>"
51+
}
52+
"</ul>"
53+
) -join [Environment]::NewLine
54+
$reply.Close($OutputEncoding.GetBytes($memberList), $false)
55+
continue nextRequest
56+
} else {
57+
$firstSegment = $segments[1] -replace '/'
58+
$mathMember = [Math]::$firstSegment
59+
if ($null -eq $mathMember) {
60+
$reply.StatusCode = 404
61+
$reply.Close()
62+
continue nextRequest
63+
}
64+
}
65+
66+
if ($mathMember.Invoke) {
67+
$mathArgs =
68+
if ($segments.Length -ge 3) {
69+
@(for ($segmentNumber = 2; $segmentNumber -lt $segments.Length; $segmentNumber++) {
70+
$segments[$segmentNumber] -replace '/' -as [double]
71+
})
72+
} else {
73+
$Reply.Close($OutputEncoding.GetBytes("$(
74+
$mathMember.OverloadDefinitions -join [Environment]::NewLine
75+
)"), $false)
76+
}
77+
try {
78+
$result = $mathMember.Invoke($mathArgs)
79+
$Reply.Close($OutputEncoding.GetBytes("$result"), $false)
80+
} catch {
81+
$Reply.Close($OutputEncoding.GetBytes("$($_ | Out-String)"), $false)
82+
}
83+
} else {
84+
$Reply.Close($OutputEncoding.GetBytes("$mathMember"), $false)
85+
}
86+
}
87+
} -ThrottleLimit 100 -ArgumentList $IO -Name "$RootUrl" | # Output our job,
88+
Add-Member -NotePropertyMembers @{ # but attach a few properties first:
89+
HttpListener=$httpListener # * The listener (so we can stop it)
90+
IO=$IO # * The IO (so we can change it)
91+
Url="$RootUrl" # The URL (so we can easily access it).
92+
} -Force -PassThru # Pass all of that thru and return it to you.

Servers/Server101.ps1

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,47 +7,46 @@
77
./Server101.ps1 ($pwd | Split-Path)
88
#>
99
param(
10-
<# The Root Directory. #> [string]$RootDirectory = $PSScriptRoot,
10+
# The Root Directory
11+
[Alias('RootDirectory')][string]$RootPath =
12+
$(if ($PSScriptRoot) {$PSScriptRoot } else { $pwd }),
1113

1214
# The rootUrl of the server. By default, a random loopback address.
13-
[string]$RootUrl=
14-
"http://127.0.0.1:$(Get-Random -Minimum 4200 -Maximum 42000)/",
15+
[string]$RootUrl="http://127.0.0.1:$(Get-Random -Minimum 4kb -Maximum 42kb)/",
1516

1617
# The type map. This determines how each extension will be served.
17-
[Collections.IDictionary]
18-
$TypeMap = [Ordered]@{
19-
".html" = "text/html" ; ".css" = "text/css" ; ".svg" = "image/svg+xml" ;
20-
".png" = "image/png" ; ".jpg" = "image/jpeg"; ".gif" = "image/gif"
21-
".mp3" = "audio/mpeg"; ".mp4" = "video/mp4"
18+
[Collections.IDictionary]$TypeMap = [Ordered]@{
19+
".html" = "text/html" ; ".css" = "text/css" ; ".svg" = "image/svg+xml"
20+
".png" = "image/png" ; ".jpg" = "image/jpeg" ; ".gif" = "image/gif"
21+
".oog" = "audio/oog" ; ".mp3" = "audio/mpeg"; ".mp4" = "video/mp4"
2222
".json" = "application/json"; ".xml" = "application/xml" ;
2323
".js" = "text/javascript" ; ".jsm" = "text/javascript" ;
2424
".ps1" = "text/x-powershell"
2525
})
2626

27-
$httpListener = [Net.HttpListener]::new()
28-
$httpListener.Prefixes.Add($RootUrl)
27+
$httpListener = [Net.HttpListener]::new();$httpListener.Prefixes.Add($RootUrl)
2928
Write-Warning "Listening on $RootUrl $($httpListener.Start())"
30-
31-
$io = [Ordered]@{ # Pack our job input into an IO dictionary
32-
HttpListener = $httpListener ; ServerRoot = $RootDirectory
33-
Files = [Ordered]@{}; ContentTypes = [Ordered]@{}
29+
# Pack our job input into an IO dictionary
30+
$io = [Ordered]@{
31+
HttpListener = $httpListener ; ServerRoot = $RootPath
32+
Files = [Ordered]@{}; ContentTypes = [Ordered]@{}
3433
}
3534
# Then map each file into one or more /uris
36-
foreach ($file in Get-ChildItem -File -Path $RootDirectory -Recurse) {
35+
foreach ($file in Get-ChildItem -File -Path $RootPath -Recurse) {
3736
$relativePath =
38-
$file.FullName.Substring($RootDirectory.Length) -replace '[\\/]', '/'
37+
$file.FullName.Substring($RootPath.Length) -replace '[\\/]', '/'
3938
$fileUris = @($relativePath) + @(
40-
foreach ($indexFile in 'index.html', 'readme.html') {
41-
$indexPattern = [Regex]::Escape($indexFile) + '$'
39+
foreach ($indexFile in 'index.html', 'readme.html') {
40+
$indexPattern = [Regex]::Escape($indexFile) + '$'
4241
if ($file.Name -eq $indexFile -and -not $IO.Files[
4342
$relativePath -replace $indexPattern
44-
]) {
43+
]) {
4544
$relativePath -replace $indexPattern
4645
$relativePath -replace "[\\/]$indexPattern"
4746
}
4847
}
4948
)
50-
foreach ($fileUri in $fileUris) {
49+
foreach ($fileUri in $fileUris) {
5150
$io.ContentTypes[$fileUri] = # and map content types now
5251
$TypeMap[$file.Extension] ? # so we don't have to later.
5352
$TypeMap[$file.Extension] :
@@ -58,44 +57,45 @@ foreach ($file in Get-ChildItem -File -Path $RootDirectory -Recurse) {
5857

5958
# Our server is a thread job
6059
Start-ThreadJob -ScriptBlock {param([Collections.IDictionary]$io)
61-
$psvariable = $ExecutionContext.SessionState.PSVariable
62-
foreach ($key in $io.Keys) { # First, let's unpack
63-
if ($io[$key] -is [PSVariable]) { $psvariable.set($io[$key]) }
64-
else { $psvariable.set($key, $io[$key]) }
65-
} # and then declare a few filters to make code more readable.
66-
filter outputError([int]$Number) {
67-
$reply.StatusCode = $Number; $reply.Close(); continue nextRequest
60+
$psvar = $ExecutionContext.SessionState.PSVariable
61+
foreach ($k in $io.Keys) { $psvar.set($k, $io[$k]) }
62+
filter outputError([int]$N) {
63+
$re.StatusCode = $N
64+
$localPath = "/$N.html";$file = $files[$LocalPath]
65+
if ($file) {outputFile} else { $re.Close() }
66+
continue next
6867
}
6968
filter outputHeader {
70-
$reply.Length=$file.Length; $reply.Close(); continue nextRequest
69+
$re.Length=$files[$localPath].Length
70+
$re.Close()
71+
continue next
7172
}
7273
filter outputFile {
7374
$reply.ContentType = $contentTypes[$localPath]
7475
$fileStream = $file.OpenRead()
7576
$fileStream.CopyTo($reply.OutputStream)
76-
$fileStream.Close(); $fileStream.Dispose(); $reply.Close()
77-
continue nextRequest
77+
$fileStream.Close(), $fileStream.Dispose()
78+
$reply.Close()
79+
continue next
7880
}
79-
# Listen for the next request
80-
:nextRequest while ($httpListener.IsListening) {
81+
# Listen for the next request and reply to it.
82+
:next while ($httpListener.IsListening) {
8183
$getContext = $httpListener.GetContextAsync()
8284
while (-not $getContext.Wait(17)) { }
83-
$request, $reply = # and reply to it.
84-
$getContext.Result.Request, $getContext.Result.Response
85-
$method, $localPath =
86-
$request.HttpMethod, $request.Url.LocalPath
85+
$rq = $request = $getContext.Result.Request
86+
$re = $reply = $getContext.Result.Response
87+
$method, $localPath = $rq.HttpMethod, $rq.Url.LocalPath
8788
# If the method is not allowed, output error 405
8889
if ($method -notin 'get', 'head') { outputError 405 }
8990
# If the file does not exist, output error 404
9091
if (-not ($files -and $files[$localPath])) { outputError 404 }
9192
$file = $files[$localPath]
9293
# If they asked for header information, output it.
93-
if ($request.httpMethod -eq 'head') { outputHeader }
94+
if ($method -eq 'head') { outputHeader }
9495
outputFile # otherwise, output the file.
9596
}
9697
} -ThrottleLimit 100 -ArgumentList $IO -Name "$RootUrl" | # Output our job,
97-
Add-Member -NotePropertyMembers @{ # but attach a few properties first:
98-
HttpListener=$httpListener # * The listener (so we can stop it)
99-
IO=$IO # * The IO (so we can change it)
100-
Url="$RootUrl" # The URL (so we can easily access it).
98+
Add-Member -NotePropertyMembers @{ # and attach a few properties:
99+
# `.HttpListener`, `.IO`, `.URL`
100+
HttpListener=$httpListener; IO=$IO; Url="$RootUrl"
101101
} -Force -PassThru # Pass all of that thru and return it to you.

Servers101.psd1

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
RootModule = 'Servers101.psm1'
1313

1414
# Version number of this module.
15-
ModuleVersion = '0.1.1'
15+
ModuleVersion = '0.1.2'
1616

1717
# Supported PSEditions
1818
# CompatiblePSEditions = @()
@@ -108,6 +108,14 @@ PrivateData = @{
108108

109109
# ReleaseNotes of this module
110110
ReleaseNotes = @'
111+
## Servers101 0.1.2:
112+
113+
* Added Error Code Support to Server101 (#18)
114+
* MathServer (#15)
115+
* README installation and streaming server instructions (#19)
116+
117+
---
118+
111119
## Servers101 0.1.1:
112120
113121
* New Servers:

0 commit comments

Comments
 (0)