Skip to content

Instantly share code, notes, and snippets.

@anonhostpi
Last active September 1, 2025 10:05
Show Gist options
  • Select an option

  • Save anonhostpi/1cc0084b959a9ea9e97dca9dce414e1f to your computer and use it in GitHub Desktop.

Select an option

Save anonhostpi/1cc0084b959a9ea9e97dca9dce414e1f to your computer and use it in GitHub Desktop.
Webserver Example
# iex (iwr "https://gist.github.com/anonhostpi/1cc0084b959a9ea9e97dca9dce414e1f/raw/webserver.ps1").Content
function Start-Webserver {
param(
[string] $Binding = "http://localhost:8080/",
[string] $BaseDirectory = "$(Get-Location -PSProvider FileSystem)",
[string] $ServerName = "PowerShell Webserver",
[System.Collections.IDictionary] $Routes = @{
Before = { param( $command, $listener, $context ) return $true }
After = { param( $command, $listener, $context ) return $true }
# "GET /" = { ... }
# "GET /hello" = "./path/to/static/file.html"
Default = {
param( $command, $request, $response )
$msg = "Hello, World!"
# $response.OutputStream.Write( [System.Text.Encoding]::UTF8.GetBytes($msg), 0, $msg.Length )
return $msg
}
}
)
$BaseDirectory = "$(Resolve-Path $BaseDirectory)"
$serve_static = {
param(
[string] $path,
$response
)
$file = "$BaseDirectory\$($path.TrimStart('/').Replace('/','\'))"
$file = "$(Resolve-Path $file)"
If( Test-Path $file ){
# Prevent directory traversal attacks
If( -not $file.StartsWith($BaseDirectory) ){
$response.StatusCode = 403
return "403 Forbidden"
}
return (Get-Content $file -Raw)
} Else {
$response.StatusCode = 404
return "404 Not Found"
}
}
If( [string]::IsNullOrWhitespace( $Routes.Default ) ) {
If( [string]::IsNullOrWhitespace( $Routes["GET /"] ) ) {
$Routes["GET /"] = { return "Hello, World!" }
}
# Simple static webfile server
$Routes.Default = {
param( $command, $request, $response )
& $serve_static $request.Url.AbsolutePath $response
}
}
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add($Binding)
$listener.Start()
$Error.Clear()
Try {
While ( $listener.IsListening ) {
$task = $listener.GetContextAsync()
$ready = $task.Wait(100) # 100 ms timeout
if (-not $ready) { continue }
$context = $task.Result
$request = $context.Request
$command = "{0} {1}" -f $request.HttpMethod, $request.Url.AbsolutePath
If( $Routes.Before ) {
$continue = & $Routes.Before $command $listener $context
If( -not $continue ) {
continue
}
}
$response = $context.Response
$response.ContentType = "text/plain; charset=utf-8"
$result = try {
If( $Routes[$command] ) {
If ( $Routes[$command] -is [scriptblock] ) {
& $Routes[$command] $command $request $response
} Else {
& $serve_static $Routes[$command] $response
}
} Else {
If( $Routes.Default -is [scriptblock] ) {
& $Routes.Default $command $request $response
} Else {
& $serve_static $Routes.Default $response
}
}
} catch {
$response.StatusCode = 500
"500 Internal Server Error`n`n$($_.Exception.Message)"
}
if( -not [string]::IsNullOrWhiteSpace($result) ) {
$buffer = [System.Text.Encoding]::UTF8.GetBytes($result)
$response.ContentLength64 = $buffer.Length
If( [string]::IsNullOrWhiteSpace($response.Headers["Last-Modified"]) ){
$response.Headers.Add("Last-Modified", (Get-Date).ToString("r"))
}
If( [string]::IsNullOrWhiteSpace($response.Headers["Server"]) ){
$response.Headers.Add("Server", $ServerName)
}
$response.OutputStream.Write( $buffer, 0, $buffer.Length )
}
$response.Close()
If( $Routes.After ) {
$null = & $Routes.After $command $listener $context
}
}
} finally {
$listener.Stop()
$listener.Close()
}
}
Start "http://localhost:8080"
Start-WebServer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment