Skip to content

Retries in WebCmdlet does not respect Retry-After header in 429 status #19621

@mkht

Description

@mkht

Prerequisites

Steps to reproduce

The improvements of the PowerShell 7.4.0 (Preview) notes that WebCmdlets (Invoke-WebRequest and Invoke-RestMethod) follow the retry interval specified in the Retry-After header for status code 429 (Too Many Requests), but it does not actually follow it.

I tested this by running a server on the local machine that always returns status 429 and Retry-After: 2.

When I run the Invoke-WebRequest with -MaximumRetryCount 2, it retries with the default retry interval of 5 seconds instead of the 2 seconds specified in Retry-After header value.

PS C:\> Invoke-Webrequest -Uri "http://localhost:8080/test" -MaximumRetryCount 2 -Verbose
VERBOSE: HTTP/1.1 GET with 0-byte payload
VERBOSE: Retrying after interval of 5 seconds. Status code for previous attempt: TooManyRequests
VERBOSE: Retrying after interval of 5 seconds. Status code for previous attempt: TooManyRequests
VERBOSE: received 5-byte response of content type text/plain

PS C:\> $Error[0].Exception.Response.StatusCode.Value__
429

PS C:\> $Error[0].Exception.Response.Headers

Key         Value
---         -----
Retry-After {2}
Server      {Microsoft-HTTPAPI/2.0}
Date        {Sat, 06 May 2023 17:25:42 GMT}

I try the same thing with curl 8.0.1, the retry is performed correctly at 2 second intervals.

C:\> curl.exe --retry 2 "http://localhost:8080/test"
RetryWarning: Problem : HTTP error. Will retry in 2 seconds. 2 retries left.
RetryWarning: Problem : HTTP error. Will retry in 2 seconds. 1 retries left.

Since this change was implemented in PR #18717, I checked the source code and found an mistake in the code. It executes the retry logic at 409 (Conflict) instead of 429.

https://github.com/CarloToso/PowerShell/blob/07e041fe10d4c753dfccfa46008bdedebdfd3027/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs#L1340-L1343

Expected behavior

PS C:\> Invoke-Webrequest -Uri "http://localhost:8080/test" -MaximumRetryCount 2 -Verbose
VERBOSE: HTTP/1.1 GET with 0-byte payload
VERBOSE: Retrying after interval of 2 seconds. Status code for previous attempt: TooManyRequests
VERBOSE: Retrying after interval of 2 seconds. Status code for previous attempt: TooManyRequests
VERBOSE: received 5-byte response of content type text/plain

Actual behavior

PS C:\> Invoke-Webrequest -Uri "http://localhost:8080/test" -MaximumRetryCount 2 -Verbose
VERBOSE: HTTP/1.1 GET with 0-byte payload
VERBOSE: Retrying after interval of 5 seconds. Status code for previous attempt: TooManyRequests
VERBOSE: Retrying after interval of 5 seconds. Status code for previous attempt: TooManyRequests
VERBOSE: received 5-byte response of content type text/plain

Error details

PS C:\> Get-Error

Exception             :
    Type       : Microsoft.PowerShell.Commands.HttpResponseException
    Response   : StatusCode: 429, ReasonPhrase: 'Too Many Requests', Version: 1.1, Content: System.Net.Http.HttpConnect
ionResponseContent, Headers:
                 {
                 Retry-After: 2
                 Server: Microsoft-HTTPAPI/2.0
                 Date: Sat, 06 May 2023 17:25:42 GMT
                 Content-Length: 5
                 Content-Type: text/plain
                 }, Trailing Headers:
                 {
                 }
    StatusCode : TooManyRequests
    TargetSite :
        Name          : ThrowTerminatingError
        DeclaringType : System.Management.Automation.MshCommandRuntime, System.Management.Automation, Version=7.4.0.3,
Culture=neutral, PublicKeyToken=31bf3856ad364e35
        MemberType    : Method
        Module        : System.Management.Automation.dll
    Message    : Response status code does not indicate success: 429 (Too Many Requests).
    Source     : System.Management.Automation
    HResult    : -2146233088
    StackTrace :
   at System.Management.Automation.MshCommandRuntime.ThrowTerminatingError(ErrorRecord errorRecord)
TargetObject          : Method: GET, RequestUri: 'http://localhost:8080/test', Version: 1.1, Content: <null>, Headers:
                        {
                        User-Agent: Mozilla/5.0
                        User-Agent: (Windows NT 10.0; Microsoft Windows 10.0.22621; ja-JP)
                        User-Agent: PowerShell/7.4.0
                        Accept-Encoding: gzip
                        Accept-Encoding: deflate
                        Accept-Encoding: br
                        }
CategoryInfo          : InvalidOperation: (Method: GET, Reques…ept-Encoding: br
                        }:HttpRequestMessage) [Invoke-WebRequest], HttpResponseException
FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
ErrorDetails          : Retry
InvocationInfo        :
    MyCommand        : Invoke-WebRequest
    ScriptLineNumber : 1
    OffsetInLine     : 1
    HistoryId        : 34
    Line             : Invoke-Webrequest -Uri "http://localhost:8080/test" -MaximumRetryCount 2 -Verbose
    Statement        : Invoke-Webrequest -Uri "http://localhost:8080/test" -MaximumRetryCount 2 -Verbose
    PositionMessage  : At line:1 char:1
                       + Invoke-Webrequest -Uri "http://localhost:8080/test" -MaximumRetryCoun …
                       + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    InvocationName   : Invoke-Webrequest
    CommandOrigin    : Internal
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1

Environment data

PS C:\> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      7.4.0-preview.3
PSEdition                      Core
GitCommitId                    7.4.0-preview.3
OS                             Microsoft Windows 10.0.22621
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Visuals

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions