Last active
January 12, 2017 12:59
-
-
Save gpduck/7716935 to your computer and use it in GitHub Desktop.
PowerShell Web Server Framework
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Start a web server that will route requests to a series of script blocks as defined by the -Routes parameter. | |
.DESCRIPTION | |
Starts a single-threaded web server and responds to requests by executing the script blocks that are | |
defined as routes on the command line. | |
.NOTES | |
Copyright 2013 Chris Duck | |
The idea for this script came from Parul Jain's work at http://poshcode.org/4073 | |
.PARAMETER Routes | |
A hashtable that maps a URL fragment to a script block to be executed for matching requests. The URL will be | |
matched as a regex to the LocalPath portion of the requested URL. Use the LocalPath property from | |
[Uri]"http://test/path?qs=test" to identify what part of your URL will be included in LocalPath. | |
The script block has 2 automatic variables available to it: | |
$Request - The [System.Net.HttpListenerRequest] object representing the incoming request. | |
$Response - The [System.Net.HttpListenerResponse] object representing the outgoing response. | |
There is also a helper function available in the script block: | |
Send-Response -Body <String> [-ContentType <String>] | |
Send-Response -Path <String> [-ContentType <String>] | |
The default content type is "text/plain". The Send-Response function will close the response | |
output stream, so the entire response must be passed as the value for the Body parameter. The | |
Path parameter reads in the contents of the supplied file (as a single string) and sends it as | |
the response. | |
To stop the web server, set $Running = $False in any of your script blocks. If the routes table | |
does not include a route with the key "Exit", a default exit route will be added. | |
If you would like to send a redirect, use: | |
$Response.Redirect("http://newurl") | |
$Response.Close() | |
.PARAMETER IPAddress | |
The IPAddress or host name to listen on. By default it will listen on all local addresses. | |
.PARAMETER Port | |
The port to listen on. By default it will listen on port 80. | |
.EXAMPLE | |
$Routes = @{ | |
"process" = { | |
Send-Response -Body (Get-Process | ConvertTo-Html) -ContentType "text/html" | |
} | |
".*" = { | |
Send-Response -Body "Unknown address, try /Process or /Exit" | |
} | |
} | |
Start-Webserver -Routes $Routes | |
This will start a server that responds to requests for /Process by listing out all the running processes in an HTML form. | |
Any other request will generate a plain text page suggesting the user try /Process or the default implementation of /Exit | |
See Get-Help Start-Webserver -Parameter Routes for more information on building routes. | |
.EXAMPLE | |
$Routes = @{ | |
"process" = { | |
if($Request.QueryString["format"] -eq "json") { | |
Send-Response -Body (Get-Process | ConvertTo-Json) -ContentType "application/json" | |
} else { | |
Send-Response -Body (Get-Process | ConvertTo-Html) -ContentType "text/html" | |
} | |
} | |
} | |
Start-Webserver -Routes $Routes -Port 8080 | |
This shows how to use parameters passed in the query string to alter your output. To have the script output JSON instead of HTML, | |
simply request /Process?format=json | |
.EXAMPLE | |
#Add the System.Web assembly so we can use System.Web.HttpUtility to UrlEncode the return URL | |
Add-Type -AssemblyName System.Web | |
$Routes = @{ | |
"GetBarcode" = { | |
$ProcessUrl = 'http://' + $Request.UserHostAddress + '/ProcessCode?code={CODE}' | |
$RedirectUrl = "zxing://scan/?ret=" + [System.Web.HttpUtility]::UrlEncode($ProcessUrl) | |
Write-Debug "Redirecting to $RedirectUrl" | |
$Response.Redirect($RedirectUrl) | |
$Response.Close() | |
} | |
"ProcessCode" = { | |
Send-Response -Body "The barcode was $($Request.QueryString['code'])" | |
} | |
} | |
Start-Webserver -Routes $Routes | |
This example creates a web server that launches Google's Barcode Scanner application when an Android phone browses to /GetBarcode. When | |
a barcode is scanned, the phone is redirected to /ProcessCode?code=THEBARCODE and a message is returned to the browser displaying the | |
value of the barcode that was scanned. | |
This shows how to use a redirect as well as process input from the user using a query string. | |
#> | |
function Start-Webserver { | |
param( | |
$Routes, | |
$IPAddress = "+", | |
$Port = "80" | |
) | |
function Send-Response { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory,ParameterSetName="Raw")] | |
[ValidateNotNull()] | |
$Body, | |
[Parameter(Mandatory,ParameterSetName="File")] | |
$Path, | |
$ContentType = "text/plain" | |
) | |
$Response.ContentType = $ContentType | |
if($PsCmdlet.ParameterSetName -eq "File") { | |
$Body = Get-Content -Path $Path -Raw | |
} | |
[byte[]]$Buffer = [System.Text.Encoding]::UTF8.GetBytes($Body) | |
$Response.ContentLength64 = $Buffer.Length | |
$Response.OutputStream.Write($Buffer, 0 , $Buffer.Length) | |
$Response.OutputStream.Close() | |
} | |
if(!$Routes.ContainsKey("Exit")) { | |
$Routes.Add("Exit", { | |
Write-Debug "Exiting" | |
$Running = $False | |
Send-Response -Body "Quitting..." | |
}) | |
} | |
$Listener = New-Object System.Net.HttpListener | |
$ListenPrefix = "http://${IPAddress}:${Port}/" | |
Write-Debug "Listening on $ListenPrefix" | |
$Listener.Prefixes.Add($ListenPrefix) | |
$Running = $true | |
try { | |
$Listener.Start() | |
while($Running) { | |
$Ctx = $Listener.GetContext() | |
$Request = $Ctx.Request | |
$Response = $Ctx.Response | |
Write-Verbose "Request accepted for $($Request.Url)" | |
$RouteFound = $false | |
foreach($Route in $Routes.Keys) { | |
if($Request.Url.LocalPath -match $Route) { | |
Write-Debug "Matched route $Route" | |
$RouteFound = $true | |
. $Routes[$Route] | |
break; | |
} else { | |
Write-Debug "Route $Route doesn't match" | |
} | |
} | |
if(!$RouteFound) { | |
Write-Warning "No route found for $($Request.Url)" | |
$Response.Close() | |
} | |
} | |
$Listener.Stop() | |
} catch { | |
Write-Error -Message "Could not start listener" -Exception $_.Exception | |
} finally { | |
if($Listener.IsListening) { | |
$Listener.Stop() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment