simple - ¿Cómo obtengo el cuerpo de una solicitud web que devolvió 400 solicitudes incorrectas de Invoke-RestMethod
solicitud de permiso (5)
Cuando corro la siguiente sentencia
Invoke-RestMethod "https://api.mysite.com/the/endpoint" `
-Body (ConvertTo-Json $data) `
-ContentType "application/json" `
-Headers $DefaultHttpHeaders `
-Method Post
el punto final devuelve 400 Bad Request
, lo que hace que PowerShell muestre el siguiente mensaje no tan útil:
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
¿Cómo obtengo el cuerpo de la respuesta, que podría decirme qué problema tenía con la solicitud que envié?
$ RespErr tendrá más detalles sobre la BadRequest en mi caso, su
$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." } }
Parece que funciona solo en localhost, probé con mi servidor real que no funcionó.
Otra forma de probar es esta
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;
}
De acuerdo con la documentación de Invoke-RestMethod , el cmdlet puede devolver diferentes tipos dependiendo del contenido que recibe. Asignar la salida del cmdlet a una variable ( $resp = Invoke-RestMethod (...)
) y luego verificar si el tipo es HtmlWebResponseObject
( $resp.gettype()
). Entonces tendrás muchas propiedades a tu disposición, como BaseResponse, Content y StatusCode.
Si $resp
es algún otro tipo (cadena, psobject y probablemente nulo en este caso), parece ser un mensaje de error. The remote server returned an error: (400) Bad Request
es el cuerpo de la respuesta, solo eliminado de html (lo probé en algunos de mis métodos), tal vez incluso truncado. Si desea extraerlo, ejecute el cmdlet usando un parámetro común para almacenar el mensaje de error: Invoke-RestMethod (...) -ErrorVariable RespErr
y lo tendrá en la variable $RespErr
.
EDITAR:
Ok, lo tengo y fue bastante obvio :). Invoke-RestMethod arroja un error, así que simplemente lo detectamos:
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;}
Aquí está todo lo que necesita, especialmente en el objeto WebResponse. He enumerado 3 propiedades que llaman la atención, hay más. Además, si almacena $_
lugar de $_.Exception
, podría haber algunas propiedades que PowerShell ya extrajo para usted, pero no espero nada más significativo que en .Exception.Response
.
Hay un problema conocido con PowerShell Invoke-WebRequest
y Invoke-RestMethod
donde el shell come el cuerpo de la respuesta cuando el código de estado es un error (4xx o 5xx). Parece que el contenido JSON que está buscando se está evaporando de esta manera. Puede obtener el cuerpo de la respuesta en su bloque catch usando $_.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
Para mí, solo funcionó en un contexto de Pester, al establecer la Posición de los flujos en 0 antes de leerlo.
$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
Si está justo después de la respuesta, StatusCode
y Content
aquí es una forma novedosa de resolver este problema sin muchas pruebas desagradables y lectura manual de las secuencias de respuesta:
# 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
}
Por lo que puedo decir, el ErrorRecord.ErrorDetails.Message
contiene el equivalente exacto a la propiedad Microsoft.PowerShell.Commands.WebResponseObject.Content
que se le devolvería en una invocación exitosa de Invoke-WebRequest
, solo sin la molestia de tener para hacer todo lo que GetResponseStream()
jazz.