< Summary - Kestrun — Combined Coverage

Information
Class: Public.Helper.Invoke-KrWebRequest
Assembly: Kestrun.PowerShell.Public
File(s): /home/runner/work/Kestrun/Kestrun/src/PowerShell/Kestrun/Public/Helper/Invoke-KrWebRequest.ps1
Tag: Kestrun/Kestrun@9d3a582b2d63930269564a7591aa77ef297cadeb
Line coverage
0%
Covered lines: 0
Uncovered lines: 108
Coverable lines: 108
Total lines: 302
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

File(s)

/home/runner/work/Kestrun/Kestrun/src/PowerShell/Kestrun/Public/Helper/Invoke-KrWebRequest.ps1

#LineLine coverage
 1<#
 2    .SYNOPSIS
 3        Sends an HTTP request to a Kestrun server over various transport mechanisms (TCP, Named Pipe, Unix Socket).
 4    .DESCRIPTION
 5        This function allows sending HTTP requests to a Kestrun server using different transport methods, including TCP,
 6        It supports various HTTP methods, custom headers, request bodies, and response handling options.
 7    .PARAMETER NamedPipeName
 8        The name of the named pipe to connect to. This parameter is mandatory when using the NamedPipe transport.
 9    .PARAMETER UnixSocketPath
 10        The file system path to the Unix domain socket. This parameter is mandatory when using the UnixSocket transport.
 11    .PARAMETER Uri
 12        The base URI of the Kestrun server. This parameter is mandatory when using the Tcp transport.
 13    .PARAMETER Method
 14        The HTTP method to use for the request (e.g., GET, POST, PUT, DELETE). The default is GET.
 15    .PARAMETER Path
 16        The request target path (e.g., '/api/resource'). Defaults to '/'.
 17    .PARAMETER Body
 18        The request body, which can be a string, byte array, or object (which will be serialized to JSON).
 19    .PARAMETER InFile
 20        The path to a file whose contents will be uploaded as the request body.
 21    .PARAMETER ContentType
 22        The content type of the request body (e.g., 'application/json').
 23    .PARAMETER Headers
 24        A hashtable of additional headers to include in the request.
 25    .PARAMETER UserAgent
 26        The User-Agent header value. Defaults to 'PowerShell/7 Kestrun-InvokeKrWebRequest'.
 27    .PARAMETER Accept
 28        The Accept header value. Defaults to '*/*'.
 29    .PARAMETER SkipCertificateCheck
 30        If specified, SSL certificate errors will be ignored (useful for self-signed certificates).
 31    .PARAMETER WebSession
 32        A hashtable containing a CookieContainer for managing cookies across requests.
 33    .PARAMETER SessionVariable
 34        The name of a variable to store the web session (cookies) for reuse in subsequent requests
 35    .PARAMETER DisallowAutoRedirect
 36        If specified, automatic redirection will be disabled.
 37    .PARAMETER MaximumRedirection
 38        The maximum number of automatic redirections to follow. Defaults to 50.
 39    .PARAMETER Credential
 40        The credentials to use for server authentication.
 41    .PARAMETER UseDefaultCredentials
 42        If specified, the default system credentials will be used for server authentication.
 43    .PARAMETER Proxy
 44        The URI of the proxy server to use for the request.
 45    .PARAMETER ProxyCredential
 46        The credentials to use for proxy authentication.
 47    .PARAMETER ProxyUseDefaultCredentials
 48        If specified, the default system credentials will be used for proxy authentication.
 49    .PARAMETER TimeoutSec
 50        The request timeout in seconds. Defaults to 100 seconds.
 51    .PARAMETER OutFile
 52        If specified, the response body will be saved to the given file path.
 53    .PARAMETER AsString
 54        If specified, the response body will be returned as a string. Otherwise, it will attempt to parse JSON if applic
 55    .PARAMETER PassThru
 56        If specified, the raw HttpResponseMessage will be returned.
 57    .EXAMPLE
 58        Invoke-KrWebRequest -Uri 'http://localhost:5000' -Method 'GET' -Path '/api/resource'
 59        Sends a GET request to the specified Kestrun server URI and path.
 60    .EXAMPLE
 61        Invoke-KrWebRequest -NamedPipeName 'MyNamedPipe' -Method 'POST' -Path '/api/resource' -Body @{ name = 'value' } 
 62        Sends a POST request with a JSON body to the Kestrun server over a named pipe.
 63    .EXAMPLE
 64        Invoke-KrWebRequest -UnixSocketPath '/var/run/kestrun.sock' -Method 'GET' -Path '/api/resource' -OutFile 'respon
 65        Sends a GET request to the Kestrun server over a Unix socket and saves the response body to a file.
 66    .NOTES
 67        This function requires the Kestrun.Net.dll assembly to be available in the same directory or a specified path.
 68        It is designed to work with Kestrun servers but can be adapted for other HTTP servers as needed.
 69#>
 70function Invoke-KrWebRequest {
 71    [CmdletBinding(DefaultParameterSetName = 'Tcp')]
 72    param(
 73        # Transport (pick one)
 74        [Parameter(Mandatory, ParameterSetName = 'NamedPipe')]
 75        [string]$NamedPipeName,
 76
 77        [Parameter(Mandatory, ParameterSetName = 'UnixSocket')]
 78        [string]$UnixSocketPath,
 79
 80        [Parameter(Mandatory, ParameterSetName = 'Tcp')]
 81        [uri]$Uri,
 82
 83        # Request
 84        [ValidateSet('GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE')]
 85        [string]$Method = 'GET',
 86        [string]$Path = '/',
 87        [object]$Body,
 88        [string]$InFile,
 89        [string]$ContentType,
 90        [hashtable]$Headers,
 91        [string]$UserAgent = 'PowerShell/7 Kestrun-InvokeKrWebRequest',
 92        [string]$Accept = '*/*',
 93        [int]$TimeoutSec = 100,
 94        [switch]$SkipCertificateCheck,
 95
 96        # Web session (cookies)
 97        [Hashtable]$WebSession,              # { CookieContainer = <System.Net.CookieContainer> }
 98        [string]$SessionVariable,
 99
 100        # Redirects
 101        [switch]$DisallowAutoRedirect,
 102        [int]$MaximumRedirection = 50,
 103
 104        # Auth (server)
 105        [pscredential]$Credential,
 106        [switch]$UseDefaultCredentials,
 107
 108        # Proxy
 109        [uri]$Proxy,
 110        [pscredential]$ProxyCredential,
 111        [switch]$ProxyUseDefaultCredentials,
 112
 113        # Output
 114        [string]$OutFile,
 115        [switch]$AsString,
 116        [switch]$PassThru
 117    )
 118
 119    # ensure DLL loaded (adjust path if needed)
 0120    if (-not ([Type]::GetType('Kestrun.Client.KrHttpClientFactory, Kestrun.Net', $false))) {
 0121        $try1 = Join-Path $PSScriptRoot '../lib/net8.0/Kestrun.Net.dll'
 0122        $try2 = Join-Path $PSScriptRoot 'Kestrun.Net.dll'
 0123        foreach ($p in @($try1, $try2)) {
 0124            $rp = Resolve-Path -EA SilentlyContinue -LiteralPath $p
 0125            if ($rp) { Add-Type -Path $rp.Path; break }
 126        }
 127    }
 128
 129    # build options for the handler
 0130    $cookieContainer = $null
 0131    if ($WebSession -and $WebSession.ContainsKey('CookieContainer')) {
 0132        $cookieContainer = $WebSession['CookieContainer']
 133    } else {
 134        # make a fresh cookie container if caller asked for a session via -SessionVariable
 0135        if ($SessionVariable) { $cookieContainer = [System.Net.CookieContainer]::new() }
 136    }
 137
 0138    $opts = [Kestrun.Client.KrHttpClientOptions]::new()
 0139    $opts.Timeout = [TimeSpan]::FromSeconds([Math]::Max(1, $TimeoutSec))
 0140    $opts.IgnoreCertErrors = $SkipCertificateCheck.IsPresent
 0141    $opts.Cookies = $cookieContainer
 0142    $opts.AllowAutoRedirect = -not $DisallowAutoRedirect.IsPresent
 0143    $opts.MaxAutomaticRedirections = [Math]::Max(1, $MaximumRedirection)
 144
 0145    if ($UseDefaultCredentials) { $opts.UseDefaultCredentials = $true }
 0146    elseif ($Credential) {
 0147        $opts.Credentials = $Credential.GetNetworkCredential()
 148    }
 149
 0150    if ($Proxy) {
 0151        $webProxy = [System.Net.WebProxy]::new($Proxy)
 0152        if ($ProxyUseDefaultCredentials) { $webProxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials }
 0153        elseif ($ProxyCredential) { $webProxy.Credentials = $ProxyCredential.GetNetworkCredential() }
 0154        $opts.Proxy = $webProxy
 0155        $opts.UseProxy = $true
 0156        $opts.ProxyUseDefaultCredentials = $ProxyUseDefaultCredentials.IsPresent
 157    }
 158
 159    # cache key (vary by transport + timeout + TLS flag + redirect + session + proxy/auth)
 0160    $sessionKey = if ($cookieContainer) { $cookieContainer.GetHashCode() } else { 0 }
 0161    $authKey = @(
 0162        $UseDefaultCredentials.IsPresent,
 163        [string]$Credential?.UserName,
 164        [string]$Proxy,
 165        $ProxyUseDefaultCredentials.IsPresent,
 166        [string]$ProxyCredential?.UserName,
 0167        (-not $DisallowAutoRedirect.IsPresent),
 168        $MaximumRedirection
 169    ) -join '|'
 170
 0171    if (-not $script:__KrIwrClients) { $script:__KrIwrClients = @{} }
 0172    $cacheKey = switch ($PSCmdlet.ParameterSetName) {
 0173        'NamedPipe' { "pipe::$NamedPipeName::$($SkipCertificateCheck.IsPresent)::$TimeoutSec::$sessionKey::$authKey" }
 0174        'UnixSocket' { "uds::$UnixSocketPath::$($SkipCertificateCheck.IsPresent)::$TimeoutSec::$sessionKey::$authKey" }
 0175        'Tcp' { "tcp::$($Uri.AbsoluteUri)::$($SkipCertificateCheck.IsPresent)::$TimeoutSec::$sessionKey::$authKey" }
 176    }
 177
 0178    if (-not $script:__KrIwrClients.ContainsKey($cacheKey)) {
 0179        $client = switch ($PSCmdlet.ParameterSetName) {
 0180            'NamedPipe' { [Kestrun.Client.KrHttpClientFactory]::CreateNamedPipeClient($NamedPipeName, $opts) }
 0181            'UnixSocket' { [Kestrun.Client.KrHttpClientFactory]::CreateUnixSocketClient($UnixSocketPath, $opts) }
 0182            'Tcp' { [Kestrun.Client.KrHttpClientFactory]::CreateTcpClient($Uri, $opts) }
 183        }
 0184        $script:__KrIwrClients[$cacheKey] = $client
 185    } else {
 0186        $client = $script:__KrIwrClients[$cacheKey]
 187    }
 188
 189    # Build request URI
 0190    $target = if ($PSCmdlet.ParameterSetName -eq 'Tcp') {
 0191        if ($Path) { [Uri]::new($client.BaseAddress, $Path) } else { $client.BaseAddress }
 192    } else {
 0193        [Uri]::new(($Path.StartsWith('/') ? $Path : "/$Path"), [System.UriKind]::Relative)
 194    }
 195
 196    # Build request
 0197    $req = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::new($Method), $target)
 0198    if ($UserAgent) { $null = $req.Headers.TryAddWithoutValidation('User-Agent', $UserAgent) }
 0199    if ($Accept) { $null = $req.Headers.TryAddWithoutValidation('Accept', $Accept) }
 0200    foreach ($k in ($Headers?.Keys ?? @())) { $null = $req.Headers.TryAddWithoutValidation([string]$k, [string]$Headers[
 201
 202    # Body / InFile
 0203    if ($InFile) {
 0204        $bytes = [System.IO.File]::ReadAllBytes((Resolve-Path -LiteralPath $InFile))
 0205        $content = [System.Net.Http.ByteArrayContent]::new($bytes)
 0206        if ($ContentType) { $content.Headers.ContentType = $ContentType }
 0207        $req.Content = $content
 0208    } elseif ($PSBoundParameters.ContainsKey('Body')) {
 0209        switch ($Body) {
 0210            { $_ -is [string] } {
 0211                $ctype = $ContentType; if (-not $ctype) { $ctype = 'text/plain; charset=utf-8' }
 0212                $req.Content = [System.Net.Http.StringContent]::new([string]$Body, [System.Text.Encoding]::UTF8, $ctype)
 213            }
 0214            { $_ -is [byte[]] } {
 0215                $req.Content = [System.Net.Http.ByteArrayContent]::new([byte[]]$Body)
 0216                if ($ContentType) { $req.Content.Headers.ContentType = $ContentType }
 217                break
 218            }
 219            default {
 0220                $json = $Body | ConvertTo-Json -Depth 32 -Compress
 0221                $req.Content = [System.Net.Http.StringContent]::new($json, [System.Text.Encoding]::UTF8, ($ContentType ?
 222            }
 223        }
 224    }
 225
 226    # Persist session if requested
 0227    if ($SessionVariable) {
 0228        if (-not $cookieContainer) { $cookieContainer = [System.Net.CookieContainer]::new() }
 229        # (Cookies are already in handler; we just hand the container out)
 0230        Set-Variable -Name $SessionVariable -Scope 1 -Value @{ CookieContainer = $cookieContainer }
 231    }
 232
 233    # ---- Send (streaming if -OutFile) ----
 0234    if ($OutFile) {
 235        # Build a fresh request for streaming (do NOT reuse across paths)
 0236        $streamReq = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::new($Method), $target)
 237        # clone headers
 0238        if ($UserAgent) { $null = $streamReq.Headers.TryAddWithoutValidation('User-Agent', $UserAgent) }
 0239        if ($Accept) { $null = $streamReq.Headers.TryAddWithoutValidation('Accept', $Accept) }
 0240        foreach ($h in ($Headers?.Keys ?? @())) { $null = $streamReq.Headers.TryAddWithoutValidation([string]$h, [string
 241        # clone content if present
 0242        if ($req.Content) {
 0243            $bytesForClone = $req.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
 0244            $streamReq.Content = [System.Net.Http.ByteArrayContent]::new($bytesForClone)
 0245            foreach ($ch in $req.Content.Headers) { $null = $streamReq.Content.Headers.TryAddWithoutValidation($ch.Key, 
 246        }
 247
 248        try {
 0249            $outPath = (Resolve-Path -LiteralPath $OutFile).Path
 0250            [Kestrun.Client.KrHttpDownloads]::DownloadToFileAsync($client, $streamReq, $outPath, $false).GetAwaiter().Ge
 251
 252            # hand back the session cookie container if requested
 0253            if ($SessionVariable) {
 0254                if (-not $cookieContainer) { $cookieContainer = [System.Net.CookieContainer]::new() }
 0255                Set-Variable -Name $SessionVariable -Scope 1 -Value @{ CookieContainer = $cookieContainer }
 256            }
 257
 0258            return [pscustomobject]@{
 0259                StatusCode = 200
 0260                StatusDescription = 'OK'
 0261                Headers = $null
 0262                RawContent = $null
 0263                Content = $null
 0264                BaseResponse = $null
 0265                SavedTo = $outPath
 266            }
 267        } finally {
 0268            $streamReq.Dispose()
 0269            if ($req) { $req.Dispose() } # dispose the original builder too
 270        }
 271    }
 272
 273    # ---- Non-file responses (beware of big bodies) ----
 274    # Standard send for non-OutFile cases; okay for JSON/text where you expect small/medium sizes.
 275    try {
 0276        $res = $client.SendAsync($req).GetAwaiter().GetResult()
 277    } finally {
 0278        $req.Dispose()
 279    }
 280
 0281    if ($PassThru) { return $res }
 282
 0283    $bytes = $res.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
 0284    $text = [System.Text.Encoding]::UTF8.GetString($bytes)
 0285    $ctype = $res.Content.Headers.ContentType?.MediaType
 286
 287    # session handoff after request completes
 0288    if ($SessionVariable) {
 0289        if (-not $cookieContainer) { $cookieContainer = [System.Net.CookieContainer]::new() }
 0290        Set-Variable -Name $SessionVariable -Scope 1 -Value @{ CookieContainer = $cookieContainer }
 291    }
 292
 0293    [pscustomobject]@{
 0294        StatusCode = [int]$res.StatusCode
 0295        StatusDescription = $res.ReasonPhrase
 0296        Headers = $res.Headers
 0297        RawContent = $text
 0298        Content = if ($ctype -and $ctype -like 'application/json*') { try { $text | ConvertFrom-Json -Depth 32 } catch {
 0299        BaseResponse = $res
 0300        SavedTo = $null
 301    }
 302}

Methods/Properties

Invoke-KrWebRequest()