Как получить тело веб-запроса, который возвратил 400 неверных запросов от Invoke-RestMethod

Когда я запускаю следующее утверждение

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: удаленный сервер возвратил ошибку: (400) неверный запрос.
В строке:1 символ:1
+ Invoke-WebRequest "https://api.mysite.com/the/endpoint" -Боди...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo: InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
    + FullyQualifiedErrorId: WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

Как получить основную часть ответа, которая может сказать мне, что не так с отправленным запросом?

9 ответов

Решение

Согласно документации Invoke-RestMethod, командлет может возвращать различные типы в зависимости от содержимого, которое он получает. Присвоение вывода командлета переменной ($resp = Invoke-RestMethod (...)), а затем проверьте, является ли тип HtmlWebResponseObject ($resp.gettype()). Тогда у вас будет много свойств, таких как BaseResponse, Content и StatusCode.

Если $resp какой-то другой тип (строка, psobject и, скорее всего, ноль в этом случае), кажется, что сообщение об ошибке The remote server returned an error: (400) Bad Request это тело ответа, только вырезанное из html (я проверял это на некоторых из моих методов), возможно, даже усеченное. Если вы хотите извлечь его, запустите командлет, используя общий параметр, чтобы сохранить сообщение об ошибке: 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,

Существует известная проблема с PowerShell Invoke-WebRequest а также Invoke-RestMethod где оболочка ест тело ответа, когда код состояния является ошибкой (4xx или 5xx). Похоже, что контент JSON, который вы ищете, испаряется именно таким образом. Вы можете получить ответное тело в вашем блоке catch, используя $_.Exception.Response.GetResponseStream()

    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

$RespErr будет иметь больше деталей о BadRequest в моем случае его

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

$RespErr;

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

Похоже, что это работает только в localhost, я пытался с моим реальным сервером, это не сработало.

Другой способ попробовать это

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

Вы можете получить HTTP-ответ без исключений, независимо от того, 200 он или 400:

Представлен Powershell 7 -SkipHttpErrorCheck

Работает как для Invoke-WebRequest а также Invoke-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 говорится:

-SkipHttpErrorCheck


Этот параметр заставляет командлет игнорировать статусы ошибок HTTP и продолжать обработку ответов. Ответы об ошибках записываются в конвейер так же, как если бы они были успешными.

Для меня это работало только в контексте Пестера, когда для потоков устанавливали позицию в 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

Если вы только после ответа StatusCode а также Content вот новый способ решения этой проблемы без большого количества грязной попытки / захвата и ручного чтения потоков ответов:

# 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() джаз.

Текстовый ответ, отправленный с сервера, содержится в переменной ошибки в следующем месте:

$_.ErrorDetails.Message

Хотя не совсем так, как предполагал ОП; У меня был аналогичный случай, когда я хотел видеть тело / заголовки запроса SENT, а не ответ. Решение - Затем вы можете проверить эту переменную и увидеть точное содержание отправленного запроса.

Вы можете работать с 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()
Другие вопросы по тегам