77 ./Server101.ps1 ($pwd | Split-Path)
88#>
99param (
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 4 kb - Maximum 42 kb ) /" ,
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 )
2928Write-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
6059Start-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.
0 commit comments