Skip to content

Commit 0968958

Browse files
feat: Server101 ( Fixes #4 )
1 parent dd1da82 commit 0968958

File tree

1 file changed

+101
-0
lines changed

1 file changed

+101
-0
lines changed

Servers/Server101.ps1

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<#
2+
.SYNOPSIS
3+
Server 101
4+
.DESCRIPTION
5+
Server 101: A file server in 101 lines of pure PowerShell.
6+
.EXAMPLE
7+
./Server101.ps1 ($pwd | Split-Path)
8+
#>
9+
param(
10+
<# The Root Directory. #> [string]$RootDirectory = $PSScriptRoot,
11+
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+
# 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"
22+
".json" = "application/json"; ".xml" = "application/xml" ;
23+
".js" = "text/javascript" ; ".jsm" = "text/javascript" ;
24+
".ps1" = "text/x-powershell"
25+
})
26+
27+
$httpListener = [Net.HttpListener]::new()
28+
$httpListener.Prefixes.Add($RootUrl)
29+
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]@{}
34+
}
35+
# Then map each file into one or more /uris
36+
foreach ($file in Get-ChildItem -File -Path $RootDirectory -Recurse) {
37+
$relativePath =
38+
$file.FullName.Substring($RootDirectory.Length) -replace '[\\/]', '/'
39+
$fileUris = @($relativePath) + @(
40+
foreach ($indexFile in 'index.html', 'readme.html') {
41+
$indexPattern = [Regex]::Escape($indexFile) + '$'
42+
if ($file.Name -eq $indexFile -and -not $IO.Files[
43+
$relativePath -replace $indexPattern
44+
]) {
45+
$relativePath -replace $indexPattern
46+
$relativePath -replace "[\\/]$indexPattern"
47+
}
48+
}
49+
)
50+
foreach ($fileUri in $fileUris) {
51+
$io.ContentTypes[$fileUri] = # and map content types now
52+
$TypeMap[$file.Extension] ? # so we don't have to later.
53+
$TypeMap[$file.Extension] :
54+
'text/plain'
55+
$io.Files[$fileUri] = $file
56+
}
57+
}
58+
59+
# Our server is a thread job
60+
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
68+
}
69+
filter outputHeader {
70+
$reply.Length=$file.Length; $reply.Close(); continue nextRequest
71+
}
72+
filter outputFile {
73+
$reply.ContentType = $contentTypes[$potentialPath]
74+
$fileStream = $file.OpenRead()
75+
$fileStream.CopyTo($reply.OutputStream)
76+
$fileStream.Close(); $fileStream.Dispose(); $reply.Close()
77+
continue nextRequest
78+
}
79+
# Listen for the next request
80+
:nextRequest while ($httpListener.IsListening) {
81+
$getContext = $httpListener.GetContextAsync()
82+
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
87+
# If the method is not allowed, output error 405
88+
if ($method -notin 'get', 'head') { outputError 405 }
89+
# If the file does not exist, output error 404
90+
if (-not ($files -and $files[$localPath])) { outputError 404 }
91+
$file = $files[$request.Url.LocalPath]
92+
# If they asked for header information, output it.
93+
if ($request.httpMethod -eq 'head') { outputHeader }
94+
outputFile # otherwise, output the file.
95+
}
96+
} -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).
101+
} -Force -PassThru # Pass all of that thru and return it to you.

0 commit comments

Comments
 (0)