关于其余部分:如何获取从Invoke-RestMethod返回400 Bad Request的Web请求的正文

How do I get the body of a web request that returned 400 Bad Request from Invoke-RestMethod

当我运行以下语句

1
2
3
4
5
Invoke-RestMethod"https://api.mysite.com/the/endpoint" `
    -Body (ConvertTo-Json $data) `
    -ContentType"application/json" `
    -Headers $DefaultHttpHeaders `
    -Method Post

端点返回400 Bad Request,这将导致PowerShell显示以下不太有用的消息:

1
2
3
4
5
6
Invoke-WebRequest : The remote server returned an error: (400) Bad Request.
At line:1 char:1
+ Invoke-WebRequest"https://api.mysite.com/the/endpoint" -Body  ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

如何获得响应的正文,这可能告诉我我发送的请求出了什么问题?


PowerShell Invoke-WebRequestInvoke-RestMethod存在一个已知问题,即状态代码为错误(4xx或5xx)时,外壳将占用响应主体。听起来您正在寻找的JSON内容正以这种方式消失。您可以使用$_.Exception.Response.GetResponseStream()在catch块中获取响应主体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    try {
    Invoke-RestMethod"https://api.mysite.com/the/endpoint" `
        -Body (ConvertTo-Json $data) `
        -ContentType"application/json" `
        -Headers $DefaultHttpHeaders `
        -Method Post
    }
    catch {
        $streamReader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream())
        $ErrResp = $streamReader.ReadToEnd() | ConvertFrom-Json
        $streamReader.Close()
    }

    $ErrResp


根据Invoke-RestMethod文档,cmdlet可以根据收到的内容返回不同的类型。将cmdlet输出关联到变量($resp = Invoke-RestMethod (...)),然后检查类型是否为HtmlWebResponseObject($resp.gettype())。然后,您将拥有许多属性,如BaseResponse,Content和StatusCode。

如果$resp是其他类型(字符串,psobject,在这种情况下很可能为null),则错误消息The remote server returned an error: (400) Bad Request似乎是响应主体,仅从html中剥离(我在某些方法中进行了测试),也许甚至被截断。如果要提取它,请使用公共参数运行cmdlet来存储错误消息:Invoke-RestMethod (...) -ErrorVariable RespErr,并将其保存在$RespErr变量中。

编辑:

好的,我明白了,这很明显:)。 Invoke-RestMethod引发错误,因此让我们赶上它:

1
2
3
4
5
6
7
8
9
10
try{$restp=Invoke-RestMethod (...)} catch {$err=$_.Exception}
$err | Get-Member -MemberType Property

  TypeName: System.Net.WebException

    Name           MemberType Definition
    ----           ---------- ----------
    Message        Property   string Message {get;}
    Response       Property   System.Net.WebResponse Response {get;}
    Status         Property   System.Net.WebExceptionStatus Status {get;}

这就是您所需要的,尤其是在WebResponse对象中。
我列出了3个引人注目的属性,还有更多。同样,如果您存储的是$_而不是$_.Exception,则可能已经为您提取了PowerShell的某些属性,但是我认为没有什么比.Exception.Response更有意义。


$ RespErr在我的案例中将包含有关BadRequest的更多详细信息

1
$responce = Invoke-RestMethod -Uri https://localhost:44377/explore/v2/Content -Method Post -Body $PostData -Headers $header -ErrorVariable RespErr;

$ RespErr;

1
{"error":{"code":"","message":"The FavoriteName field is required." } }

看来它仅在localhost中有效,我尝试在我的实际服务器上不起作用。

另一种尝试的方法是

1
2
3
4
5
6
7
8
9
10
11
12
13
    try{
$response =""
$response = Invoke-WebRequest -Uri https://contentserverint-mhdev.azurewebsites.net/apis/explore/v2/Content?overwrite=true -Method Post -Body $PostData -Headers  $header -ErrorVariable RespErr
#$response = Invoke-RestMethod -Uri https://localhost:44377/explore/v2/Content?overwrite=true -Method Post -Body $PostData -Headers  $header -ErrorVariable RespErr
Write-Host"Content created with url="$response.value[0]

}
catch [System.Net.WebException] {  
        $respStream = $_.Exception.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($respStream)
        $respBody = $reader.ReadToEnd() | ConvertFrom-Json
        $respBody;
 }


对我来说,它仅在Pester上下文中有效,在读取之前将流Position设置为0时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
        $statusCode = $null
        $responseBody = $null
        try {
            $response = Invoke-RestMethod -Method GET -Uri"$($apiPrefix)$($operation)" -Headers $headers
            }
        catch [System.Net.WebException] {
            $statusCode = $_.Exception.Response.StatusCode
            $respStream = $_.Exception.Response.GetResponseStream()
            $reader = New-Object System.IO.StreamReader($respStream)
            $reader.BaseStream.Position = 0
            $responseBody = $reader.ReadToEnd() | ConvertFrom-Json
        }
        $statusCode | Should Be $Expected
        $responseBody | Should Not Be $null

如果您只是在响应StatusCodeContent之后,这是一种解决此问题的新颖方法,而无需进行很多混乱的try / catch和手动读取响应流的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Place the trap within your chosen scope (e.g. function or script)
trap [Net.WebException] { continue; }

# Exceptions are no longer thrown here
$response = Invoke-WebRequest $endpoint

# Check if last command failed
if (!$?)
{  
    # $error[0] now contains the ErrorRecord of the last error (in this case from Invoke-WebRequest)
    # Note: $response should be null at this point

    # Due to the magic of Microsoft.PowerShell.Commands.InvokeWebRequestCommand.WebCmdletWebResponseException
    # we can get the response body directly from the ErrorDetails field
    $body = $error[0].ErrorDetails.Message

    # For compatibility with $response.StatusCode lets cast to int    
    $statusCode = [int] $error[0].Exception.Response.StatusCode
}

据我所知,ErrorRecord.ErrorDetails.Message包含与Microsoft.PowerShell.Commands.WebResponseObject.Content属性完全相同的属性,该属性将在成功调用Invoke-WebRequest时返回给您,而不必麻烦做所有GetResponseStream()爵士乐。