Files
yovision/Bell/scripts/runtime/bell-web.ps1
T

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()
}