Private/Resolve-MyWebApiContext.ps1
|
function Get-MyWebApiContextProperty { [CmdletBinding()] param( [Parameter(Mandatory)][object] $Context, [Parameter(Mandatory)][string] $Name ) if ($Context -is [System.Collections.IDictionary]) { return $Context[$Name] } $property = $Context.PSObject.Properties[$Name] if ($property) { return $property.Value } return $null } function Set-MyWebApiContextProperty { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][object] $Context, [Parameter(Mandatory)][string] $Name, [AllowNull()][object] $Value ) if (-not $PSCmdlet.ShouldProcess($Name, 'Update session property')) { return } if ($Context -is [System.Collections.IDictionary]) { $Context[$Name] = $Value return } $property = $Context.PSObject.Properties[$Name] if ($property) { $property.Value = $Value } else { $Context | Add-Member -NotePropertyName $Name -NotePropertyValue $Value } } function Resolve-MyWebApiContext { [CmdletBinding()] param([object] $Connection) $context = if ($null -ne $Connection) { $Connection } else { $script:MyWebApiContext } if ($null -eq $context) { throw 'Not connected. Call Connect-MyWebApi first, or pass a Connection returned by Connect-MyWebApi.' } if (-not (Get-MyWebApiContextProperty -Context $context -Name 'BaseUrl')) { throw 'The supplied Connection is not a valid MyWebApi session.' } return $context } function Assert-MyWebApiEndpoint { [CmdletBinding()] param( [Parameter(Mandatory)][string] $Value, [Parameter(Mandatory)][string] $Name, [Parameter(Mandatory)][object] $Context ) $uri = $null if (-not [Uri]::TryCreate($Value, [UriKind]::Absolute, [ref]$uri)) { throw "$Name must be an absolute HTTPS URI." } $allowLoopback = [bool](Get-MyWebApiContextProperty -Context $Context -Name 'AllowInsecureLoopback') $isLoopback = $uri.IsLoopback -or ($uri.Host -in @('localhost', '127.0.0.1', '::1')) if ($uri.Scheme -ne 'https' -and -not ($uri.Scheme -eq 'http' -and $allowLoopback -and $isLoopback)) { throw "$Name must use HTTPS. HTTP is allowed only for an explicitly enabled loopback test endpoint." } return $uri } function Get-MyWebApiTimeoutSeconds { [CmdletBinding()] param( [Parameter(Mandatory)][object] $Context, [ValidateRange(1, 600)][int] $Default = 30 ) $value = Get-MyWebApiContextProperty -Context $Context -Name 'HttpTimeoutSeconds' if ($null -eq $value -or [int]$value -lt 1) { return $Default } return [Math]::Min([int]$value, 600) } function Get-MyWebApiOrigin { [CmdletBinding()] param([Parameter(Mandatory)][Uri] $Uri) return ('{0}://{1}' -f $Uri.Scheme, $Uri.Authority) } function Get-MyWebApiCancellationToken { [CmdletBinding()] param([Parameter(Mandatory)][object] $Context) $source = Get-MyWebApiContextProperty -Context $Context -Name 'CancellationSource' if ($source) { return $source.Token } return [System.Threading.CancellationToken]::None } function Assert-MyWebApiNotCancelled { [CmdletBinding()] param([Parameter(Mandatory)][object] $Context) $token = Get-MyWebApiCancellationToken -Context $Context if ($token.IsCancellationRequested) { throw [System.OperationCanceledException]::new('The WebAPI session operation was cancelled.') } } function Invoke-MyWebApiHttpJson { <# Sends one bounded HTTP request with a real linked CancellationToken. Invoke-RestMethod in PowerShell 7.4 exposes no cancellation token, so this helper owns HttpClient and links the per-session cancellation source with a hard operation deadline. #> [CmdletBinding()] param( [Parameter(Mandatory)][object] $Context, [Parameter(Mandatory)][ValidateSet('GET','POST','PATCH','PUT','DELETE','HEAD','OPTIONS')][string] $Method, [Parameter(Mandatory)][string] $Uri, [hashtable] $Headers, [object] $JsonBody, [hashtable] $FormBody ) Assert-MyWebApiNotCancelled -Context $Context $timeout = Get-MyWebApiTimeoutSeconds -Context $Context $parentToken = Get-MyWebApiCancellationToken -Context $Context $linked = [System.Threading.CancellationTokenSource]::CreateLinkedTokenSource($parentToken) $linked.CancelAfter([TimeSpan]::FromSeconds($timeout)) $handler = [System.Net.Http.HttpClientHandler]::new() $handler.AllowAutoRedirect = $false $client = [System.Net.Http.HttpClient]::new($handler) $client.Timeout = [System.Threading.Timeout]::InfiniteTimeSpan $request = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::new($Method), $Uri) try { if ($Headers) { foreach ($key in $Headers.Keys) { [void]$request.Headers.TryAddWithoutValidation([string]$key, [string]$Headers[$key]) } } if ($null -ne $JsonBody) { $json = $JsonBody | ConvertTo-Json -Depth 20 $request.Content = [System.Net.Http.StringContent]::new($json, [System.Text.Encoding]::UTF8, 'application/json') } elseif ($null -ne $FormBody) { $pairs = [System.Collections.Generic.List[System.Collections.Generic.KeyValuePair[string,string]]]::new() foreach ($key in $FormBody.Keys) { $pairs.Add([System.Collections.Generic.KeyValuePair[string,string]]::new([string]$key, [string]$FormBody[$key])) } $request.Content = [System.Net.Http.FormUrlEncodedContent]::new($pairs) } try { $response = $client.SendAsync($request, [System.Net.Http.HttpCompletionOption]::ResponseContentRead, $linked.Token).GetAwaiter().GetResult() $raw = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() } catch [System.OperationCanceledException] { if ($parentToken.IsCancellationRequested) { throw } throw [System.TimeoutException]::new("HTTP $Method $Uri exceeded the $timeout second deadline.") } $status = [int]$response.StatusCode if ($status -lt 200 -or $status -ge 300) { $httpError = [System.Net.Http.HttpRequestException]::new("HTTP $status from $Method $Uri") $httpError.Data['StatusCode'] = $status $httpError.Data['ResponseBody'] = $raw throw $httpError } if ([string]::IsNullOrWhiteSpace($raw)) { return $null } return ($raw | ConvertFrom-Json -Depth 100) } finally { $request.Dispose() $client.Dispose() $handler.Dispose() $linked.Dispose() } } |