Repository navigation
Expand file tree
/
Copy pathRequestEngine.psm1
More file actions
103 lines (98 loc) · 4.34 KB
/
Copy pathRequestEngine.psm1
File metadata and controls
103 lines (98 loc) · 4.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
Set-StrictMode -Version Latest
function Start-RequestEngine {
[CmdletBinding()]
param(
[Parameter(Mandatory)][scriptblock]$Handler,
[ValidateRange(1024, 65535)][int]$Port = 8088,
[ValidateRange(1, 32)][int]$Workers = 8,
[ValidateRange(0, 1024)][int]$QueueCapacity = 16,
[ValidateRange(50, 60000)][int]$RequestTimeoutMilliseconds = 2000,
[ValidateRange(128, 1048576)][int]$MaxResponseBytes = 16384
)
$listener = [System.Net.HttpListener]::new()
$prefix = 'http://127.0.0.1:{0}/' -f $Port
$listener.Prefixes.Add($prefix)
$listener.IgnoreWriteExceptions = $true
$stop = [System.Threading.CancellationTokenSource]::new()
$cancel = [System.Collections.Concurrent.ConcurrentQueue[string]]::new()
$state = [hashtable]::Synchronized(@{ Snapshot = $null; Failure = $null })
$settings = @{ Workers = $Workers; QueueCapacity = $QueueCapacity
TimeoutMs = $RequestTimeoutMilliseconds; MaxResponseBytes = $MaxResponseBytes }
$dispatcher = $null
$runspace = $null
try {
$listener.Start()
$runspace = [runspacefactory]::CreateRunspace()
$runspace.Open()
$dispatcher = [powershell]::Create()
$dispatcher.Runspace = $runspace
$source = [IO.File]::ReadAllText((Join-Path $PSScriptRoot 'Private/Dispatcher.ps1'))
$null = $dispatcher.AddScript($source).AddArgument($listener).AddArgument($Handler.ToString()).
AddArgument($settings).AddArgument($state).AddArgument($stop).AddArgument($cancel)
$async = $dispatcher.BeginInvoke()
$engine = [pscustomobject]@{ PSTypeName = 'DerekPoe.RequestEngine'; Uri = $prefix
Dispatcher = $dispatcher; Runspace = $runspace; Async = $async
StopSource = $stop; CancelQueue = $cancel; State = $state; Cleaned = $false }
$startup = [Diagnostics.Stopwatch]::StartNew()
while ($null -eq $state.Snapshot -and -not $async.IsCompleted -and $startup.ElapsedMilliseconds -lt 5000) {
Start-Sleep -Milliseconds 5
}
if ($null -eq $state.Snapshot) { throw 'Request engine dispatcher failed to start.' }
return $engine
} catch {
$stop.Cancel()
$listener.Close()
if ($dispatcher) { $dispatcher.Dispose() }
if ($runspace) { $runspace.Dispose() }
$stop.Dispose()
throw
}
}
function Get-RequestEngineState {
[CmdletBinding()]
param([Parameter(Mandatory)]$Engine)
# Return detached records, never the mutable dispatcher state or HTTP contexts.
$snapshot = $Engine.State.Snapshot
if ($null -eq $snapshot) { return $null }
$copy = [ordered]@{}
foreach ($property in $snapshot.PSObject.Properties) {
if ($property.Name -eq 'Requests') {
$copy.Requests = @($property.Value | ForEach-Object {
[pscustomobject]@{ Id = $_.Id; Path = $_.Path; State = $_.State }
})
} else { $copy[$property.Name] = $property.Value }
}
[pscustomobject]$copy
}
function Stop-RequestEngineRequest {
[CmdletBinding()]
param([Parameter(Mandatory)]$Engine, [Parameter(Mandatory)][guid]$RequestId)
if ($Engine.Cleaned) { return }
$Engine.CancelQueue.Enqueue($RequestId.ToString())
}
function Stop-RequestEngine {
[CmdletBinding()]
param([Parameter(Mandatory)]$Engine,
[ValidateRange(0, 60000)][int]$WaitMilliseconds = 5000)
if (-not $Engine.Cleaned) {
$Engine.StopSource.Cancel()
$finished = $Engine.Async.IsCompleted
if (-not $finished -and $WaitMilliseconds -gt 0) {
$finished = $Engine.Async.AsyncWaitHandle.WaitOne($WaitMilliseconds)
}
if ($finished) {
try { $null = $Engine.Dispatcher.EndInvoke($Engine.Async) }
catch { $Engine.State.Failure = 'Dispatcher terminated unexpectedly.' }
finally {
$Engine.Dispatcher.Dispose()
$Engine.Runspace.Dispose()
$Engine.StopSource.Dispose()
$Engine.Cleaned = $true
}
}
}
# False Stopped means a trusted handler has not cooperated with cancellation.
# Its dispatcher still owns the runspace; call Stop again to finish disposal.
Get-RequestEngineState -Engine $Engine
}
Export-ModuleMember -Function Start-RequestEngine, Get-RequestEngineState, Stop-RequestEngineRequest, Stop-RequestEngine