private/Get-RequestData.ps1
|
function Get-RequestData { <# .SYNOPSIS Executes a web request and returns the response with automatic retry on transient failures. .DESCRIPTION This function performs a web request and returns the response. It supports various HTTP methods such as GET, POST, PUT, DELETE, and PATCH, and allows customization of the request parameters. Implements automatic retry logic for transient HTTP errors (502, 503, 504, etc.) with exponential backoff to gracefully handle temporary server issues. SECURITY NOTE: String parameters are validated and sanitized to prevent injection attacks. .PARAMETER URI Specifies the URI for the web request. .PARAMETER AuthString Specifies the authentication string. .PARAMETER Method Specifies the REST API method (GET, POST, PUT, DELETE, PATCH). .PARAMETER Body Specifies the request's body. .PARAMETER ContentType Specifies the content type of the request (default is "application/json"). .PARAMETER IgnoreCertificateErrors Ignores certificate errors if this switch is present. .PARAMETER MaxRetries Specifies the maximum number of retry attempts for transient errors (default is 3). Valid Range: 0 to 10 Applies to HTTP status codes: 502 (Bad Gateway), 503 (Service Unavailable), 504 (Gateway Timeout). .EXAMPLE Get-RequestData -URI $URI -AuthString $AuthString -IgnoreCertificateErrors .EXAMPLE Get-RequestData -URI $URI -AuthString $AuthString -Method POST -Body $Body .EXAMPLE Get-RequestData -URI $URI -AuthString $AuthString -MaxRetries 5 .INPUTS None. You cannot pipe objects to Get-RequestData. .OUTPUTS Array of custom objects returned by the remote server. .NOTES Version 1.0.0 SECURITY: Implements OData injection prevention and input validation. RELIABILITY: Automatic retry with exponential backoff for transient failures (v0.1.5). #> [CmdletBinding()] param ( [parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string] $URI, [parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string] $AuthString, [parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)] [ValidateSet('GET', 'POST', 'PUT', 'DELETE', 'PATCH')] [string] $Method = 'GET', [parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string] $Body, [parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string] $ContentType = 'application/json', [parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)] [ValidateRange(0, 10)] [int] $MaxRetries = 3, [switch] $IgnoreCertificateErrors ) # Constructing a log message for verbose and debug output $LogMessage = "URI: '$URI': Method '$Method'" # Creating authorization headers $AuthHeader = @{ Authorization = $AuthString } # Creating request parameters $WebRequestParams = @{ Uri = $URI Method = $Method Headers = $AuthHeader } # Adding body and content type if the Body parameter is specified if ($Body) { $WebRequestParams.Add('Body', $Body) $WebRequestParams.Add('ContentType', $ContentType) } # Outputting log messages to verbose and debug streams if ($PSCmdlet.MyInvocation.BoundParameters['Debug']) { "Get-RequestData. $LogMessage" | Write-Debug "$_ `nJSON:$($WebRequestParams | ConvertTo-Json -Depth 3 | Out-String)" | Write-Debug } if ($PSCmdlet.MyInvocation.BoundParameters['Verbose']) { Write-Verbose "Get-RequestData. $LogMessage" } # Retry logic for transient failures [int]$RetryCount = 0 [bool]$IsTransientError = $false $LastException = $null do { try { # Ignore certificate errors if the switch is present if ($IgnoreCertificateErrors) { [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy } # Performing the web request $Response = Invoke-RestMethod @WebRequestParams -ErrorAction Stop $IsTransientError = $false # Outputting additional debug information if ($PSCmdlet.MyInvocation.BoundParameters['Debug']) { "$($MyInvocation.MyCommand). Response:`n{0}" -f $Response | Out-String | Write-Debug } # Checking if the response indicates success if ($Response.ResponseCode -match "(^40\d|^50\d)") { $errorContext = @( "ERROR: API returned error response" "Method: $Method" "URI: $URI" "Response Code: $($Response.ResponseCode)" "Response Error: $($Response.Error)" "Response Result: $($Response.Result)" "Timestamp: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" ) -join "`n" Write-Error $errorContext throw "API Error (Code: $($Response.ResponseCode)): $($Response.Error). Check logs for details." } if (($Response.ResponseCode -match "(^0$)|(^20\d+$)") -or ('OK' -eq $Response.Status)) { return $Response } else { # Handling errors and throwing an exception Write-Error "ERROR`nResponse Code: '$($Response.ResponseCode)'`nResponse Error: '$($Response.Error)'`nResponse Result: '$($Response.Result)'" throw $("Response Code: '$($Response.ResponseCode)'`nResponse Error: '$($Response.Error)'") } } catch [System.Net.WebException] { # Check if this is a transient error (502, 503, 504) $statusCode = [int]$_.Exception.Response.StatusCode if ($statusCode -match '^(502|503|504)$' -and $RetryCount -lt $MaxRetries) { $IsTransientError = $true $LastException = $_ $RetryCount++ # Calculate exponential backoff: 1s, 2s, 4s, 8s, etc. $WaitSeconds = [Math]::Pow(2, $RetryCount - 1) $statusCodeName = GetStatusCodeName -StatusCode $statusCode Write-Warning "WARNING: Failed to fetch page (attempt $RetryCount of $MaxRetries): Exception calling `"EnsureSuccessStatusCode`" with `"0`" argument(s): `"Response status code does not indicate success: $statusCode ($statusCodeName).`"`nRetrying in $WaitSeconds seconds..." if ($PSCmdlet.MyInvocation.BoundParameters['Verbose']) { Write-Verbose "Transient error detected (HTTP $statusCode). Retrying in $WaitSeconds seconds. (Attempt $RetryCount of $MaxRetries)" } Start-Sleep -Seconds $WaitSeconds } else { # Handling web exceptions with detailed context $errorContext = @( "NETWORK ERROR: Failed to call REST API endpoint" "Method: $Method" "URI: $URI" "Error Message: $($_.Exception.Message)" "Status Code: $($_.Exception.Response.StatusCode)" "Timestamp: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" "Suggestion: Verify network connectivity, URI is correct, and authentication credentials are valid." ) -join "`n" Write-Error $errorContext throw $_ } } } while ($IsTransientError -and $RetryCount -le $MaxRetries) } # Helper function to get human-readable status code names function GetStatusCodeName { param([int]$StatusCode) switch ($StatusCode) { 502 { return "Bad Gateway" } 503 { return "Service Unavailable" } 504 { return "Gateway Timeout" } default { return "HTTP Error" } } } #endregion function Get-RequestData |