PowerShell 7:用本地回环区分 HTTP 错误、超时和连接失败
Invoke-WebRequest 请求失败时,并非都能拿到 HTTP 状态码。服务端返回 503 与连接尚未建立就失败,是两类不同的结果。下面用一个只监听 127.0.0.1 的临时服务,让它们和超时在同一次测试中出现。
环境:Windows 11、PowerShell 7.6.7、.NET 10.0.13。示例要求 PowerShell 7.4 或更高版本及 Start-ThreadJob;仅在上述版本实测。无需外网、账号或安装依赖,不要把本地演示服务暴露到公网。
在 PowerShell 7 中一次性粘贴运行:
& {
$ErrorActionPreference = 'Stop'
if ($PSVersionTable.PSVersion -lt [version]'7.4') {
throw 'Requires PowerShell 7.4 or later; verified with PowerShell 7.6.7'
}
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
$listener.ExclusiveAddressUse = $true
$job = $null
try {
# Bind on loopback only, with an automatically selected port.
$listener.Start()
$port = ([System.Net.IPEndPoint]$listener.LocalEndpoint).Port
$job = Start-ThreadJob -ArgumentList $listener -ScriptBlock {
param($server)
$ErrorActionPreference = 'Stop'
# Test order: first response 200, second 503, third deliberately stalls.
foreach ($status in @('200 OK', '503 Service Unavailable', 'slow')) {
$client = $server.AcceptTcpClient()
try {
if ($status -eq 'slow') {
Start-Sleep -Seconds 3
continue
}
$stream = $client.GetStream()
$reader = [System.IO.StreamReader]::new($stream, [System.Text.Encoding]::ASCII, $false, 1024, $true)
try {
while (($line = $reader.ReadLine()) -ne '') { if ($null -eq $line) { break } }
$data = [System.Text.Encoding]::ASCII.GetBytes("HTTP/1.1 $status`r`nContent-Length: 2`r`nConnection: close`r`n`r`nok")
$stream.Write($data, 0, $data.Length)
} finally { $reader.Dispose() }
} finally { $client.Dispose() }
}
}
foreach ($name in @('ok', 'error', 'slow', 'down')) {
if ($name -eq 'down') {
# Wait for the stalled connection to close before shutting down the listener.
$null = $job | Wait-Job -Timeout 6
if ($job.State -ne 'Completed') { throw 'Local server did not finish normally' }
$listener.Stop()
}
$timeoutSeconds = if ($name -eq 'down') { 5 } else { 1 }
# Labels identify requests; the tiny server responds in this order, not by routing.
try {
$response = Invoke-WebRequest -Uri "http://127.0.0.1:$port/$name" -NoProxy -TimeoutSec $timeoutSeconds -ErrorAction Stop
[pscustomobject]@{ Case = $name; Outcome = 'success'; HTTP = [int]$response.StatusCode; Kind = 'None'; Detail = '-' }
} catch {
$kind = $_.Exception.GetType().Name
$inner = $_.Exception.InnerException
$detail = if ($inner -is [System.Net.Sockets.SocketException]) { $inner.SocketErrorCode.ToString() } else { '-' }
if ($null -ne $_.Exception.Response) {
[pscustomobject]@{ Case = $name; Outcome = 'http-error'; HTTP = [int]$_.Exception.Response.StatusCode; Kind = $kind; Detail = $detail }
} elseif ($_.Exception -is [System.OperationCanceledException]) {
[pscustomobject]@{ Case = $name; Outcome = 'timeout'; HTTP = $null; Kind = $kind; Detail = $detail }
} elseif ($_.Exception -is [System.Net.Http.HttpRequestException]) {
[pscustomobject]@{ Case = $name; Outcome = 'request-failed'; HTTP = $null; Kind = $kind; Detail = $detail }
} else { throw }
}
}
} finally {
$listener.Stop()
if ($null -ne $job) { $job | Stop-Job; $job | Remove-Job -Force }
}
} | Format-Table -AutoSize
一次实际输出如下(空白的 HTTP 列表示没有收到可用状态码):
Case Outcome HTTP Kind Detail
---- ------- ---- ---- ------
ok success 200 None -
error http-error 503 HttpResponseException -
slow timeout TaskCanceledException -
down request-failed HttpRequestException ConnectionRefused
ok 收到 200;error 收到 503,默认进入 catch,可以从 Exception.Response.StatusCode 获取状态码。slow 的服务端接受连接后暂停 3 秒,不发送响应;客户端的 1 秒限制先到,出现 TaskCanceledException。down 等待慢连接结束、关闭监听器,再请求同一端口;本次内层 SocketException 的代码为 ConnectionRefused。它是本机服务未监听时的连接失败,不代表所有不可达网络都会返回同一个错误。
这里的 HTTP 服务刻意简化:前三次连接按顺序处理,路径只是标签,不根据路径路由。脚本只监听回环地址,-NoProxy 防止本地请求绕到代理,端口由系统临时分配;finally 停止监听器和线程任务。关闭监听器与重新请求之间理论上仍有其他进程占用同一端口的竞态,极少数情况下应重跑,不应把意外成功当作连接失败。
down 使用 5 秒限制,是因为本机实测较短限制可能先超时,来不及得到拒绝连接错误;其他机器、版本或防火墙的表现可能不同。示例把 OperationCanceledException 归为超时只适合本次没有外部取消的场景,生产代码还要考虑主动取消;HttpRequestException 则可能来自 DNS、TLS 等其他阶段,不能一概判定为“服务未启动”。实际响应还可能包含重定向或读取流中断,本文没有覆盖。
PowerShell 7.4 起 -TimeoutSec 是 -ConnectionTimeoutSeconds 的别名;-OperationTimeoutSeconds 控制流读取中的等待间隔,两者不等同。示例仅模拟等待响应头时的超时,没有验证流读取超时。详情见 Microsoft Learn 的 Invoke-WebRequest 文档。
AI 协助说明: 本文和示例由 AI 协助起草与编写,代码已在上述本机环境实际执行并核对四种结果;这些结果不是云服务的实测数据。
- 点赞
- 收藏
- 关注作者
评论(0)