Skip to content

Commit 86434eb

Browse files
author
James Brundage
committed
feat: Get-WebSocket -RootURL/-HostHeader ( Fixes #47 )
1 parent 0a31f20 commit 86434eb

File tree

1 file changed

+134
-3
lines changed

1 file changed

+134
-3
lines changed

Commands/Get-WebSocket.ps1

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,109 @@ function Get-WebSocket {
403403
Write-Error $_
404404
}
405405
}
406+
}
407+
$SocketServerJob = {
408+
<#
409+
.SYNOPSIS
410+
A fairly simple WebSocket server
411+
.DESCRIPTION
412+
A fairly simple WebSocket server
413+
#>
414+
param(
415+
# By accepting a single parameter containing variables,
416+
# we can avoid the need to pass in a large number of parameters.
417+
# we can also modify this dictionary, to provide a way to pass information back.
418+
[Collections.IDictionary]$Variable
419+
)
420+
421+
# Take every every `-Variable` passed in and define it within the job
422+
foreach ($keyValue in $variable.GetEnumerator()) {
423+
$ExecutionContext.SessionState.PSVariable.Set($keyValue.Key, $keyValue.Value)
424+
}
425+
426+
# If there's no listener, create one.
427+
if (-not $httpListener) {
428+
$httpListener = $variable['HttpListener'] = [Net.HttpListener]::new()
429+
}
430+
431+
# If the listener doesn't have a lookup table for SocketRequests, create one.
432+
if (-not $httpListener.SocketRequests) {
433+
$httpListener.psobject.properties.add(
434+
[psnoteproperty]::new('SocketRequests', [Ordered]@{}), $true)
435+
}
436+
437+
# If the listener isn't listening, start it.
438+
if (-not $httpListener.IsListening) { $httpListener.Start() }
439+
440+
# While the listener is listening,
441+
while ($httpListener.IsListening) {
442+
# get the context asynchronously.
443+
$contextAsync = $httpListener.GetContextAsync()
444+
# and wait for it to complete.
445+
while (-not ($contextAsync.IsCompleted -or $contextAsync.IsFaulted -or $contextAsync.IsCanceled)) {
446+
# while this is going on, other events can be processed, and CTRL-C can exit.
447+
}
448+
# If async method fails,
449+
if ($contextAsync.IsFaulted) {
450+
# write an error and continue.
451+
Write-Error -Exception $contextAsync.Exception -Category ProtocolError
452+
continue
453+
}
454+
# Get the context async result.
455+
# The context is basically the next request and response in the queue.
456+
$context = $(try { $contextAsync.Result } catch { $_ })
457+
$RequestedUrl = $context.Request.Url
458+
# Favicons are literally outdated, but they're still requested.
459+
if ($RequestedUrl -match '/favicon.ico$') {
460+
# by returning a 404 for them, we can make the browser stop asking.
461+
$context.Response.StatusCode = 404
462+
$context.Response.Close()
463+
continue
464+
}
465+
# Now, for the fun part.
466+
# We turn request into a PowerShell events.
467+
# Each event will have the source identifier of the request scheme
468+
$eventIdentifier = "$($context.Request.Url.Scheme)://"
469+
# and by default it will pass a message containing the context.
470+
$messageData = [Ordered]@{Url = $context.Request.Url;Context = $context}
471+
472+
# HttpListeners are quite nice, especially when it comes to websocket upgrades.
473+
# If the request is a websocket request
474+
if ($context.Request.IsWebSocketRequest) {
475+
# we will change the event identifier to a websocket scheme.
476+
$eventIdentifier = $eventIdentifier -replace '^http', 'ws'
477+
# and call the `AcceptWebSocketAsync` method to upgrade the connection.
478+
$acceptWebSocket = $context.AcceptWebSocketAsync('json')
479+
# Once again, we'll use a tight loop to wait for the upgrade to complete or fail.
480+
while (-not ($acceptWebSocket.IsCompleted -or $acceptWebSocket.IsFaulted -or $acceptWebSocket.IsCanceled)) { }
481+
# and if it fails,
482+
if ($acceptWebSocket.IsFaulted) {
483+
# we will write an error and continue.
484+
Write-Error -Exception $acceptWebSocket.Exception -Category ProtocolError
485+
continue
486+
}
487+
# If it succeeds, capture the result.
488+
$webSocketResult = try { $acceptWebSocket.Result } catch { $_ }
489+
# and add it to the SocketRequests lookup table, using the request trace identifier as the key.
490+
$httpListener.SocketRequests[$context.Request.RequestTraceIdentifier] = $webSocketResult
491+
# and add the websocketcontext result to the message data.
492+
$messageData["WebSocketContext"] = $webSocketResult
493+
# also add the websocket result to the message data, since many might not exactly know what a "WebSocketContext" is.
494+
$messageData["WebSocket"] = $webSocketResult.WebSocket
495+
}
496+
497+
# Now, we generate the event.
498+
$generateEventArguments = @(
499+
$eventIdentifier,
500+
$httpListener,
501+
@($context)
502+
$messageData
503+
)
504+
# Get a pointer to the GenerateEvent method (we'll want this later)
505+
if ($MainRunspace.Events.GenerateEvent) {
506+
$MainRunspace.Events.GenerateEvent.Invoke($generateEventArguments)
507+
}
508+
}
406509
}
407510
}
408511

@@ -411,6 +514,34 @@ function Get-WebSocket {
411514
foreach ($keyValuePair in $PSBoundParameters.GetEnumerator()) {
412515
$Variable[$keyValuePair.Key] = $keyValuePair.Value
413516
}
517+
518+
$Variable['MainRunspace'] = [Runspace]::DefaultRunspace
519+
520+
# If we're going to be listening for HTTP requests, run a thread job for the server.
521+
if ($RootUrl) {
522+
523+
$httpListener = $variable['HttpListener'] = [Net.HttpListener]::new()
524+
foreach ($rootUrl in $RootUrl) {
525+
if ($rootUrl -match '^https?://') {
526+
$httpListener.Prefixes.Add($rootUrl)
527+
} else {
528+
$httpListener.Prefixes.Add("http://$rootUrl/")
529+
$httpListener.Prefixes.Add("https://$rootUrl/")
530+
}
531+
}
532+
$httpListener.Start()
533+
$httpListenerJob = Start-ThreadJob -ScriptBlock $SocketServerJob -Name "$RootUrl" -InitializationScript $InitializationScript -ArgumentList $Variable
534+
535+
if ($httpListenerJob) {
536+
foreach ($keyValuePair in $Variable.GetEnumerator()) {
537+
$httpListenerJob.psobject.properties.add(
538+
[psnoteproperty]::new($keyValuePair.Key, $keyValuePair.Value), $true
539+
)
540+
}
541+
$httpListenerJob
542+
}
543+
}
544+
414545
# If `-Debug` was passed,
415546
if ($DebugPreference -notin 'SilentlyContinue','Ignore') {
416547
# run the job in the current scope (so we can debug it).
@@ -477,15 +608,15 @@ function Get-WebSocket {
477608
$webSocketJob.pstypenames.insert(0, 'WebSocket.ThreadJob')
478609
}
479610

480-
if ($Watch) {
611+
if ($Watch -and $webSocketJob) {
481612
do {
482613
$webSocketJob | Receive-Job
483614
Start-Sleep -Milliseconds (
484615
7, 11, 13, 17, 19, 23 | Get-Random
485616
)
486617
} while ($webSocketJob.State -in 'Running','NotStarted')
487618
}
488-
elseif ($WatchFor) {
619+
elseif ($WatchFor -and $webSocketJob) {
489620
. {
490621
do {
491622
$webSocketJob | Receive-Job
@@ -512,7 +643,7 @@ function Get-WebSocket {
512643
}
513644
}
514645
}
515-
else {
646+
elseif ($webSocketJob) {
516647
$webSocketJob
517648
}
518649
}

0 commit comments

Comments
 (0)