Invoke-WebRequest で 4xx を受け取ったときに投げられる例外とその対応
TL;DR
-
Invoke-WebRequest
で 4xx などの応答を受け取ると例外を投げる - PowerShell 7 であれば
-SkipHttpErrorCheck
パラメーターを使うことでその応答を受け取っても例外を投げないようにできる - PowerShell 5.1 だと例外を処理するしかない
はじめに
PowerShell には Invoke-WebRequest
という cmdlet があり、curl
のように HTTP リクエストを送ることができます。
まず、以下のような HTTP request を送ってみます。
try {
$response = Invoke-WebRequest -Uri "https://learn.microsoft.com/en-us/azure/expressroute/expressroute-faqs"
$response.StatusCode
} catch {
$_.exception
}
正しい URL へのアクセスであれば、$response
にその応答が格納され、200 (HTTP status code で OK の意味) が出力されます。
> try {
>> $response = Invoke-WebRequest -Uri "https://learn.microsoft.com/en-us/azure/expressroute/expressroute-faqs"
>> $response.StatusCode
>> } catch {
>> $_.exception
>> }
200
次に、少し URL を変えて、存在しないページにアクセスしてみます。
少しわかりづらいですが、expressroute-faqs
から expressroute-faq
に変えています。
try {
$response = Invoke-WebRequest -Uri "https://learn.microsoft.com/en-us/azure/expressroute/expressroute-faq"
$response.StatusCode
} catch {
$_.exception
}
するとなんということでしょう、例外が投げられます。
> try {
>> $response = Invoke-WebRequest -Uri "https://learn.microsoft.com/en-us/azure/expressroute/expressroute-faq"
>> $response.StatusCode
>> } catch {
>> $_.exception
>> }
Response : StatusCode: 404, ReasonPhrase: 'Not Found', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
Request-Context: appId=cid-v1:b1c5b6ea-7ff0-41d3-9862-84c5e1dc3be7
X-Datacenter: wus
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-UA-Compatible: IE=edge
X-XSS-Protection: 1; mode=block
X-Rendering-Stack: Dynamic
nel: {"report_to":"network-errors","max_age":604800,"success_fraction":0.01,"failure_fraction":1.0}
report-to: {"group":"network-errors","max_age":604800,"endpoints":[{"url":"https://mdec.nelreports.net/api/report?cat=mdocs"}]}
X-Azure-Ref: 0ohTbZQAAAADdBV7XAaZaSp4kbLw3grWOVFlPMDFFREdFMjMxOAA3MTY4OTIwZS05ZjViLTRhNjItYjE2ZS1kNWJlNjNjZTYxZTc=
Cache-Control: public, max-age=600
Date: Sun, 25 Feb 2024 10:21:22 GMT
Connection: keep-alive
Akamai-Cache-Status: Miss from child, Miss from parent
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Type: text/html
Content-Length: 26839
Expires: Sun, 25 Feb 2024 10:31:22 GMT
}
HttpRequestError : Unknown
StatusCode : NotFound
TargetSite : Void ThrowTerminatingError(System.Management.Automation.ErrorRecord)
Message : Response status code does not indicate success: 404 (Not Found).
Data : {}
InnerException :
HelpLink :
Source : System.Management.Automation
HResult : -2146233088
StackTrace : at System.Management.Automation.MshCommandRuntime.ThrowTerminatingError(ErrorRecord errorRecord)
4xx status code の意味とその対応
404 (Not Found) の応答が返ってきていること、それはサーバーからは正常に応答を受け取っており、その内容として「そのリソースは存在しない」ということを示しています。
ということは、この程度で例外を投げてほしいわけではなく、その応答を受け取って処理を続けたい、ということがあります。
今回、例として 404 (Not Found) を対象としていますが、429 (Too Many Requests) の場合にはその応答に含まれた時間だけ待ってからリトライする、ということはよくあります。
どうするか
Invoke-WebRequest
には -SkipHttpErrorCheck
というパラメーターがあり、これを使うことで 4xx などの応答を受け取っても例外を投げないようにできます。
> try {
>> $response = Invoke-WebRequest -Uri "https://learn.microsoft.com/en-us/azure/expressroute/expressroute-faq" -SkipHttpErrorCheck
>> $response.StatusCode
>> } catch {
>> $_.exception
>> }
404
これにより、404 であればそのリソースがないと判断して別の処理を続けたり、429 であればどこかのヘッダーに示された時間だけ待ってからリトライする、といったことができます。
PowerShell 5.1 だとどうするか
例外を処理するしかないので、こんな感じになります。
status code は $_.Exception.Response.StatusCode.Value__
というちょっと深いところに格納されています。
$maxRetries = 10
$retryCount = 0
while ($retryCount -lt $maxRetries) {
try {
$response = Invoke-WebRequest -Uri $url -Method Post -Headers $headers -Body $body
break
} catch {
# if return code is 429, wait for 10 seconds and retry
if ($_.Exception.Response.StatusCode.Value__ -eq 429) {
Start-Sleep -Seconds 10
$retryCount++
} else {
throw $_
}
}
}
まとめ
既定では、Invoke-WebRequest
は 4xx などの応答を受け取ると例外を投げます。
そこで、-SkipHttpErrorCheck
パラメーターを使うことでその応答を受け取っても例外を投げないようにできます。
参考
Invoke-WebRequest
- api - Invoke-Restmethod: how do I get the return code? - Stack Overflow
value__
は typo じゃないよ、って書かれてます、普通にそう思っちゃいますよね
Discussion