powershell 如何获取从Invoke-RestMethod返回400 Bad Request的Web请求的主体

1qczuiv0  于 2023-03-23  发布在  Shell
关注(0)|答案(9)|浏览(230)

当我运行下面的语句时

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

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

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

如何获取响应的主体,它可能会告诉我发送的请求有什么问题?

j9per5c4

j9per5c41#

PowerShell Invoke-WebRequestInvoke-RestMethod存在一个已知问题,当状态代码为错误(4xx或5xx)时,shell会吃掉响应正文。听起来您正在寻找的JSON内容就是以这种方式蒸发的。您可以使用$_.Exception.Response.GetResponseStream()在catch块中获取响应正文

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
jgwigjjp

jgwigjjp2#

根据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中剥离(我在我的一些方法上测试了这一点),甚至可能被截断。如果您想要提取它,请使用common参数运行cmdlet以存储错误消息:Invoke-RestMethod (...) -ErrorVariable RespErr,它将存在$RespErr变量中。
编辑:
好了,我明白了,这是很明显的:). Invoke-RestMethod抛出一个错误,所以让我们抓住它:

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更有意义的属性。

yuvru6vn

yuvru6vn3#

$RespErr将提供有关BadRequest的更多详细信息,在我的示例中,它

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

$响应错误;

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

它看起来像它只在本地主机工作,我尝试与我的实际服务器它不工作。
另一个尝试的方法是

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;
 }
1tu0hz3e

1tu0hz3e4#

无论是200还是400,都可以毫无例外地获得HTTP响应:
Powershell 7推出-SkipHttpErrorCheck👏
它适用于Invoke-WebRequestInvoke-RestMethod

PS> $res = Invoke-WebRequest -SkipHttpErrorCheck -Method POST https://login.microsoftonline.com/does-not-exist/oauth2/token
PS> $res    
                                                                                                                                                                                                                                                          
StatusCode        : 400                                                                                                                  
StatusDescription : BadRequest                                                                                                           
Content           : {"error":"invalid_request","error_description":"AADSTS900144: The request body must contain the following parameter:
                     'grant_type'.\r\nTrace ID: f40877fd-ae34-4b95-a8d4-c7b8ba613801\r\nCorrelation ID: …
RawContent        : HTTP/1.1 400 BadRequest
                    Cache-Control: no-store, no-cache
                    Pragma: no-cache
                    Strict-Transport-Security: max-age=31536000; includeSubDomains
                    X-Content-Type-Options: nosniff
                    P3P: CP="DSP CUR OTPi IND OTRi…
Headers           : {[Cache-Control, System.String[]], [Pragma, System.String[]], [Strict-Transport-Security, System.String[]], [X-Conte
                    nt-Type-Options, System.String[]]…}
Images            : {}
InputFields       : {}
Links             : {}
RawContentLength  : 503
RelationLink      : {}

MS文档中指出:

  • 跳过HttpError检查
    此参数使cmdlet忽略HTTP错误状态并继续处理响应。错误响应将像成功一样写入管道。
piv4azn7

piv4azn75#

对我来说,它只在一个纠缠上下文中工作,当设置流位置为0之前阅读它。

$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
z4iuyo4d

z4iuyo4d6#

如果你只是在响应StatusCodeContent之后,这里有一个解决这个问题的新方法,不需要大量混乱的try/catch和手动阅读响应流:

# 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()爵士乐。

py49o6xq

py49o6xq7#

从服务器发送的文本响应包含在error变量中的以下位置:

$_.ErrorDetails.Message
fnatzsnv

fnatzsnv8#

虽然不完全符合OP的预期;我有一个类似的例子,想看到发送请求的主体/头,而不是响应。解决方案是-SessionVariable VarNameHere-然后您可以检查这个变量,看到发送请求的确切内容。

uqxowvwt

uqxowvwt9#

您可以使用HTTPClient来获得原始响应:

# initialise the HTTP client:
Add-Type -AssemblyName System.Net.Http
$ignoreCerts = [System.Net.Http.HttpClientHandler]::DangerousAcceptAnyServerCertificateValidator
$handler = [System.Net.Http.HttpClientHandler]::new()
$handler.ServerCertificateCustomValidationCallback = $ignoreCerts
$client = [System.Net.Http.HttpClient]::new($handler)

# define types for sending form-data via POST:
$kvPair = [System.Collections.Generic.KeyValuePair[string,string]]
$kvList = [System.Collections.Generic.List[System.Collections.Generic.KeyValuePair[string,string]]]

$url = "https://api.mysite.com/the/endpoint"

# create the body for the web-call:
$form = $kvList::new()
$form.Add($kvPair::new('grant_type', 'password'))
$form.Add($kvPair::new('username', $user))
$form.Add($kvPair::new('password', $pass))
$content = [System.Net.Http.FormUrlEncodedContent]::new($form)

# send the POST command:
$result = $client.PostAsync($url, $content).Result
$response = $result.Content.ReadAsStringAsync().Result
write-host $response

# close-out:
$client.Dispose()
$handler.Dispose()

相关问题