Как получить тело веб-запроса, который возвратил 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()