private/http/Invoke-WebRequestWithErrorHandling.ps1

# $params =[Hashtable]@{
# "Uri" = "https://requestly.dev/api/mockv2/helloworld?rq_uid=UyRFxSA8PHPgJg6VKNz2tQZYlI23"
# }

# TODO maybe put all http functions into one module

<#
$wr = @( Invoke-RestMethodWithErrorHandling -Params $params )
#>


function Invoke-WebRequestWithErrorHandling {
    [CmdletBinding()]
    param (
         [Parameter(Mandatory=$true)][Hashtable]$Params
        ,[Parameter(Mandatory=$false)][Array]$RetryHttpErrorList = [Array]@(502)    # http errors that should used for $maxTriesSpecific
        ,[Parameter(Mandatory=$false)][int]$MaxTriesSpecific = 3                    # Specific http errors that are catched, see $RetryHttpErrorList
        ,[Parameter(Mandatory=$false)][int]$MaxTriesGeneric = 1                     # Generic errors that are not specifically catched
        ,[Parameter(Mandatory=$false)][int]$MillisecondsDelay = 200                 # Delay for the case of an exception
        ,[Parameter(Mandatory=$false)][Switch]$ForceUTF8Return = $false             # Sometimes the returned result is not correctly encoded, this switch fixes it
    )

    begin {

        # Clear the error object
        $Error.Clear()
        $completed = $false

    }

    process {

        $response = $null
        $specificCounter = 0
        $genericCounter = 0
        do {

            try {

                If ( $ForceUTF8Return -eq $true ) {
                    $response = Invoke-WebRequestUTF8 @Params -ErrorAction Stop -UseBasicParsing
                } else {
                    $response = Invoke-WebRequest @Params -ErrorAction Stop -UseBasicParsing
                }
                $completed = $true

            } catch {

                $e = $_

                # parse the response code and body
                $errResponse = $e.Exception.Response
                $errBody = Import-ErrorForResponseBody -Err $e

                #$errResponse.StatusCode.value__ #= 502
                #$errResponse.StatusCode.ToString() # = "BadGateway"
                #$errResponse.ReasonPhrase # = "Bad Gateway"

                # directly throw an exception so we can catch it in the caller
                if ( $errResponse.StatusCode.value__ -eq 401 ) {
                    throw $e #.exception
                }

                # retry if a specific http error happens
                if ( $RetryHttpErrorList -contains $errResponse.StatusCode.value__ ) {

                    $specificCounter += 1

                    # Exceeded all retries
                    if ($specificCounter -ge $MaxTriesSpecific) {
                        Write-Log -Message "Request $( $specificCounter ) failed with $( $errResponse.StatusCode.value__ ) $( $errResponse.StatusCode.ToString() ). Command failed the maximum number of $( $MaxTriesSpecific ) times."  -Severity WARNING
                        #Write-Log -Message $_.Exception.Message -Severity ERROR
                        Write-Log -Message "RESPONSE: $( ConvertTo-Json -InputObject $errBody -Depth 99 -Compress)" -Severity WARNING
                        throw $e #.Exception

                    # Not all specific tries used yet, repeat
                    } else {
                        Write-Log -Message "Request $( $specificCounter ) failed with $( $errResponse.StatusCode.value__ ) $( $errResponse.StatusCode.ToString() ). Retrying in $( $MillisecondsDelay ) milliseconds."
                        Start-Sleep -Milliseconds $MillisecondsDelay
                        Continue
                    }

                # generic problems
                } else {

                    $genericCounter += 1

                    # Exceeded all retries
                    if ($genericCounter -ge $MaxTriesGeneric) {
                        Write-Log -Message "Request $( $genericCounter ) failed. Command failed the maximum number of $( $MaxTriesGeneric ) times." -Severity WARNING
                        #Write-Log -Message $_.Exception.Message -Severity ERROR
                        Write-Log -Message "RESPONSE: $( ConvertTo-Json -InputObject $errBody -Depth 99 -Compress)"
                        throw $e #.Exception

                    # Not all generic tries used yet, repeat
                    } else {
                        Write-Log -Message "Request $( $genericCounter ) failed. Retrying in $( $MillisecondsDelay ) milliseconds." -Severity WARNING
                        Start-Sleep -Milliseconds $MillisecondsDelay
                        Continue
                    }

                }

            }

        } until ( $completed -eq $true -or $specificCounter -ge $MaxTriesSpecific -or $genericCounter -ge $MaxTriesGeneric)

    }

    end {

        # Clear the error object
        $Error.Clear()

        # Return: Make sure it is not really null to imitate Invoke-RestMethod
        # Give the whole object back so we can read headers and more information
        $response

    }

}