I have got trouble fetching the content of error responses (400/500) when using Symfony http client (RetryableHttpClient) and AsyncResponse:
The content is always empty, no matter if I do getContent() within a try/catch statement or use getContent(false) (false=dont throw an exception on error status code). The documentation states that getContent() will wait for the whole response to be fetched, even when using streamed/async Responses.
If I test the same request i.e. via Postman the response content is a json, as expected.
Example V1:
/* #var Symfony\Component\HttpClient\RetryableHttpClient $client */
$response = $client->request('POST', '/something' , [
'auth_bearer' => '******',
'body' => [
"some" => "thing"
]
]);
/* #var Symfony\Component\HttpClient\Response\AsyncResponse $response */
p_r($response->getContent(false)); // empty
Example V2 (try/catch):
try {
... see V1
$response->getContent(); // throws exception
} catch (ClientExceptionInterface|ServerExceptionInterface $exception) {
p_r($exception->getResponse()->getContent(false)); // empty
}
can you trying the toArray ? in the documentation for payload Json it is better to get the toArray link
$exception->getResponse()->toArray(false)
Related
I want to make a request to retrieve user info from OAUTH server with Symfony HttpClient but I can't fetch fetch the response directly when encountering an error response, because the client throws an exception.
My UserProvider:
public function loadUserByUsername($username)
{
try {
$response = $this->httpClient->request(
'GET',
$this->baseUrl . '/userinfo',
[
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json'
],
'auth_bearer' => $username
]
);
var_dump($response->toArray());die;
} catch (\Exception $e) {
var_dump($e->getMessage());die;
throw new UsernameNotFoundException($e->getMessage());
}
}
When I call $response->toArray() or $response->getContent(), an exception is thrown and I get the following error message on Postman
<pre class='xdebug-var-dump' dir='ltr'>
<small>/var/www/html/backend/src/Security/Provider/KeycloakUserProvider.php:50:</small><small>string</small> <font color='#cc0000'>'HTTP/2 401 returned for "https://oauth_server/userinfo".'</font> <i>(length=127)</i>
</pre>
An example of the response received on the browser:
{"error":"invalid_request","error_description":"Token not provided"}
Why can't I access the response directly by calling $response->toArray()?
Whenever the status code of the response is not "good" (e.g. in the 300-599 range), the response is considered exceptional, and you are supposed to handle it.
This is clearly documented here:
When the HTTP status code of the response is in the 300-599 range (i.e. 3xx, 4xx or 5xx), the getHeaders(), getContent() and toArray() methods throw an appropriate exception, all of which implement the HttpExceptionInterface.
To opt-out from this exception and deal with 300-599 status codes on your own, pass false as the optional argument to every call of those methods, e.g. $response->getHeaders(false);.
If you do not want the client to throw an exception when encountering a non-OK response, you need to pass false to getContent() or toArray().
E.g.
$rawResponse = $response->getContents(false);
$arrayResponse = $response->toArray(false);
There is nothing wrong in handling the exception explicitly in your code, and will make your application better express the conditions it encounters. A "non-good" should be treated as anomalous, and handled accordingly.
I created a simple API in Lumen (application A) which:
receives PSR-7 request interface
replaces URI of the request to the application B
and sends the request through Guzzle.
public function apiMethod(ServerRequestInterface $psrRequest)
{
$url = $this->getUri();
$psrRequest = $psrRequest->withUri($url);
$response = $this->httpClient->send($psrRequest);
return response($response->getBody(), $response->getStatusCode(), $response->getHeaders());
}
The above code passes data to the application B for the query params, x-www-form-urlencoded, or JSON content type. However, it fails to pass the multipart/form-data. (The file is available in the application A: $psrRequest->getUploadedFiles()).
Edit 1
I tried replacing the Guzzle invocation with the Buzz
$psr18Client = new Browser(new Curl(new Psr17Factory()), new Psr17Factory());
$response = $psr18Client->sendRequest($psrRequest);
but still, it does not make a difference.
Edit 2
Instances of ServerRequestInterface represent a request on the server-side. Guzzle and Buzz are using an instance of RequestInterface to send data. The RequestInterface is missing abstraction over uploaded files. So files should be added manually http://docs.guzzlephp.org/en/stable/request-options.html#multipart
$options = [];
/** #var UploadedFileInterface $uploadedFile */
foreach ($psrRequest->getUploadedFiles() as $uploadedFile) {
$options['multipart'][] = [
'name' => 'file',
'fileName' => $uploadedFile->getClientFilename(),
'contents' => $uploadedFile->getStream()->getContents()
];
}
$response = $this->httpClient->send($psrRequest, $options);
But still no luck with that.
What I am missing? How to change the request so files will be sent properly?
It seems that $options['multipart'] is taken into account when using post method from guzzle. So changing the code to the $response = $this->httpClient->post($psrRequest->getUri(), $options); solves the problem.
Also, it is important not to attach 'content-type header.
I am having trouble with adding error output to the JWT middleware set up.
I am getting this error: Cannot use object of type Slim\Http\Response as array
I am using Slim 3 and the slim-jwt-auth package, I am using the sample code in the docs found at https://github.com/tuupola/slim-jwt-auth#error
The difference being I'm calling \Slim\Middleware\JwtAuthentication instead of Tuupola\Middleware\JwtAuthentication. If I use that the class cannot be found. Everything was working fine until I wanted to add the error output to the middleware set up, here is my code:
$app->add(new \Slim\Middleware\JwtAuthentication([
"path" => "/mypath",
"passthrough" => "/mypath/get-auth",
"secret" => getenv("SKEY"),
"secure" => false,
"error" => function ($response, $args) {
$data = array();
$data["status"] = "error";
$data["message"] = $args["message"];
return $response
->withHeader("Content-Type", "application/json")
->getBody()->write(
json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
]));
The error output says it's coming from the line $data["message"] = $args["message"];.
Am I looking right at the problem and not seeing it?
The function signature for the "error" closure is:
function ($request, $response, $args): Response
You're missing the first parameter in your code, so when you use $args, you're getting the Response object.
I'm making a Remote::get request to an API which answers with http 500 when something goes wrong. The thing is that it also gives {errorCode: x} as a more detailed description of what went wrong in the response text. On some of the error codes I need to take different actions.
My problem is that Kohana throws an exception on http 500 responses and thus bakes in my easy parsable response text in a "wordy" error message in the exception object.
Is there some way to get the response text of the Remote::get on a http 500 response without having to parse a lengthy error description?
Impossible. Take a look at source code
if ($code AND $code < 200 OR $code > 299)
{
$error = $response;
}
...
if (isset($error))
{
throw new Kohana_Exception('Error fetching remote :url [ status :code ] :error',
array(':url' => $url, ':code' => $code, ':error' => $error));
}
Kohana_Exception doesn't help much
public function __construct($message, array $variables = NULL, $code = 0)
{
// Set the message
$message = __($message, $variables);
// Pass the message to the parent
parent::__construct($message, $code);
}
So it mixes all things into one message.
How about using different HTTP client? For example Guzzle - it is easier to retrieve the body on error.
I have seen many examples on how to set headers on a response but I cannot find a way to inspect the headers of a response.
For example in a test case I have:
public function testGetJson()
{
$response = $this->action('GET', 'LocationTypeController#index', null, array('Accept' => 'application/json'));
$this->assertResponseStatus(200);
//some code here to test that the response content-type is 'application/json'
}
public function testGetXml()
{
$response = $this->action('GET', 'LocationTypeController#index', null, array('Accept' => 'text/xml'));
$this->assertResponseStatus(200);
//some code here to test that the response content-type is 'text/xml'
}
How would I go about testing that the content-type header is 'application/json' or any other content-type? Maybe I'm misunderstanding something?
The controllers I have can do content negation with the Accept header and I want to make sure the content type in the response is correct.
Thanks!
After some digging around in the Symfony and Laravel docs I was able to figure it out...
public function testGetJson()
{
// Symfony interally prefixes headers with "HTTP", so
// just Accept would not work. I also had the method signature wrong...
$response = $this->action('GET', 'LocationTypeController#index',
array(), array(), array(), array('HTTP_Accept' => 'application/json'));
$this->assertResponseStatus(200);
// I just needed to access the public
// headers var (which is a Symfony ResponseHeaderBag object)
$this->assertEquals('application/json',
$response->headers->get('Content-Type'));
}
While not specifically about testing, a nice way of getting at Laravel's response object is to register a 'Finish' callback. These are executed just after the response is delivered, right before the app closes. The callback receives the request and the response objects as arguments.
App::finish(function($request, $response) {
if (Str::contains($response->headers->get('content-type'), 'text/xml') {
// Response is XML
}
}
Take a look at the laravel documentation
Request::header('accept'); // or
Response::header('accept');
Retrieving A Request Header
$value = Request::header('Content-Type');
Another way would be to use getallheaders() :
var_dump(getallheaders());
// array(8) {
// ["Accept"]=>
// string(63) "text/html[...]"
// ["Accept-Charset"]=> ...
For debugging purposes You could simply use this:
var_dump($response->headers);