73 lines
4.2 KiB
PowerShell
73 lines
4.2 KiB
PowerShell
param(
|
|
[Parameter(Mandatory = $true)][string]$WebRoot,
|
|
[Parameter(Mandatory = $true)][string]$ListenHost,
|
|
[Parameter(Mandatory = $true)][int]$ListenPort,
|
|
[Parameter(Mandatory = $true)][string]$BackendUrl
|
|
)
|
|
Set-StrictMode -Version 3.0
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$root = [IO.Path]::GetFullPath($WebRoot).TrimEnd('\') + '\'
|
|
$listener = [Net.HttpListener]::new()
|
|
$listener.Prefixes.Add("http://$ListenHost`:$ListenPort/")
|
|
$handler = [Net.Http.HttpClientHandler]::new()
|
|
$handler.UseProxy = $false
|
|
$client = [Net.Http.HttpClient]::new($handler)
|
|
$mime = @{ '.html'='text/html; charset=utf-8'; '.js'='application/javascript; charset=utf-8'; '.css'='text/css; charset=utf-8'; '.json'='application/json; charset=utf-8'; '.svg'='image/svg+xml'; '.png'='image/png'; '.jpg'='image/jpeg'; '.jpeg'='image/jpeg'; '.gif'='image/gif'; '.ico'='image/x-icon'; '.woff'='font/woff'; '.woff2'='font/woff2'; '.ttf'='font/ttf'; '.eot'='application/vnd.ms-fontobject' }
|
|
|
|
try {
|
|
$listener.Start()
|
|
Write-Host "Bell web listening at http://$ListenHost`:$ListenPort/"
|
|
while ($listener.IsListening) {
|
|
$context = $listener.GetContext()
|
|
try {
|
|
$request = $context.Request
|
|
$response = $context.Response
|
|
$path = $request.Url.AbsolutePath
|
|
if ($path -eq '/healthz' -or $path.StartsWith('/api/')) {
|
|
$target = "$BackendUrl$($request.Url.PathAndQuery)"
|
|
$message = [Net.Http.HttpRequestMessage]::new([Net.Http.HttpMethod]::new($request.HttpMethod), $target)
|
|
if ($request.HasEntityBody) {
|
|
$memory = [IO.MemoryStream]::new()
|
|
$request.InputStream.CopyTo($memory)
|
|
$message.Content = [Net.Http.ByteArrayContent]::new($memory.ToArray())
|
|
$memory.Dispose()
|
|
}
|
|
foreach ($key in $request.Headers.AllKeys) {
|
|
if ($key -in @('Host','Content-Length')) { continue }
|
|
$values = $request.Headers.GetValues($key)
|
|
if (-not $message.Headers.TryAddWithoutValidation($key, $values) -and $null -ne $message.Content) { [void]$message.Content.Headers.TryAddWithoutValidation($key, $values) }
|
|
}
|
|
$upstream = $client.SendAsync($message).GetAwaiter().GetResult()
|
|
$bytes = $upstream.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
|
|
$response.StatusCode = [int]$upstream.StatusCode
|
|
if ($upstream.Content.Headers.ContentType) { $response.ContentType = $upstream.Content.Headers.ContentType.ToString() }
|
|
$response.ContentLength64 = $bytes.Length
|
|
$response.OutputStream.Write($bytes, 0, $bytes.Length)
|
|
$message.Dispose(); $upstream.Dispose()
|
|
} else {
|
|
$relative = [Uri]::UnescapeDataString($path.TrimStart('/')).Replace('/', '\')
|
|
if ([string]::IsNullOrWhiteSpace($relative)) { $relative = 'index.html' }
|
|
$file = [IO.Path]::GetFullPath((Join-Path $root $relative))
|
|
if (-not $file.StartsWith($root, [StringComparison]::OrdinalIgnoreCase)) { $response.StatusCode = 403 }
|
|
elseif (-not (Test-Path -LiteralPath $file -PathType Leaf)) {
|
|
$file = Join-Path $root 'index.html'
|
|
}
|
|
if ($response.StatusCode -ne 403) {
|
|
$bytes = [IO.File]::ReadAllBytes($file)
|
|
$extension = [IO.Path]::GetExtension($file).ToLowerInvariant()
|
|
$response.ContentType = $(if ($mime.ContainsKey($extension)) { $mime[$extension] } else { 'application/octet-stream' })
|
|
$response.ContentLength64 = $bytes.Length
|
|
$response.OutputStream.Write($bytes, 0, $bytes.Length)
|
|
}
|
|
}
|
|
} catch {
|
|
try { $context.Response.StatusCode = 502; $bytes = [Text.Encoding]::UTF8.GetBytes('Bell web gateway error'); $context.Response.ContentLength64 = $bytes.Length; $context.Response.OutputStream.Write($bytes,0,$bytes.Length) } catch {}
|
|
} finally {
|
|
try { $context.Response.OutputStream.Close() } catch {}
|
|
}
|
|
}
|
|
} finally {
|
|
$client.Dispose(); $handler.Dispose(); try { $listener.Stop() } catch {}; $listener.Close()
|
|
}
|