Guzzlehttp 7.3 JSON_UNESCAPED_SLASHES not working (Lumen/Laravel) - php

I am having an issue with an app created in Lumen and Guzzlehttp requests.
Looks like I cannot pass options like JSON_UNESCAPED_SLASHES whenever I am doing a request:
$response = (new Client())->request($this->typeRequest, $endpoint, $options);
This is hitting my server with escaped slashes ("one\/two") and causing some troubles.
Everything seems to be related with the vendor/guzzlehttp/guzzle/src/Client.php into applyOptions() function which is using jsonEncode and not giving the option to pass anything:
$options['body'] = Utils::jsonEncode($options['json']);
This can be easily fixed just putting the option into jsonEncode:
$options['body'] = Utils::jsonEncode($options['json'], JSON_UNESCAPED_SLASHES);
The issue here is in case I am updating something with composer then will be override.
How can I resolve an issue like this?

You can just pass body directly:
['body'=>json_encode($data, JSON_UNESCAPED_SLASHES)].
use GuzzleHttp\Psr7\Request;
//...
$request = new Request('POST', $endpoint, ['content-type' => 'application/json'], json_encode($data, JSON_UNESCAPED_SLASHES));
$response = (new Client())->send($request);

Related

Can't Use on_stats Option Using Laravel HTTP Client

Currently, I'm using Laravel HTTP client to make a request to an external URL. Mostly, the package working fine until I try to implement on_stats option from Guzzle.
From the doc, it says we can use Guzzle options using withMethod() method.
Here is my sample code to implement on_stats option using HTTP client.
$response = Http::withOptions([
'debug' => true,
'on_stats' => function(\GuzzleHttp\TransferStats $stats) {
Log::debug($stats->getTransferTime());
}
])
->get('https://laravel.com');
dd($response->status());
The code above will produce an error with the message:
Second array member is not a valid method
However, when I'm using the option within the Guzzle package directly, it works fine.
$client = new \GuzzleHttp\Client;
$response = $client->get('https://laravel.com', [
'on_stats' => function(\GuzzleHttp\TransferStats $stats) {
Log::debug($stats->getTransferTime());
}
]);
dd((string) $response->getStatusCode());
Any idea why this is happening? Is it a bug from the HTTP client wrapper from Laravel?
FYI, I'm using Laravel 8.x.
Thanks.
withOptions uses this code:
return tap($this, function ($request) use ($options) {
return $this->options = array_merge_recursive($this->options, $options);
});
So I'm guessing passing a closure in may not work, since it's not actually an array. From https://laracasts.com/discuss/channels/requests/httpwithtoken-get-total-time-of-request , you can get it from the response instead, so try this:
$client = new \GuzzleHttp\Client;
$response = $client->get('https://laravel.com');
Log::debug($response->transferStats->getTransferTime());

How get a valid JSON output from the response in Zend Framework 3?

I'm writing a client for an API...
use Zend\Http\Client;
use Zend\Http\Request;
use Zend\Json\Json;
...
$request = new Request();
$request->getHeaders()->addHeaders([
'Accept-Charset' => 'UTF-8',
'Accept' => 'application/hal+json',
'Content-Type' => 'application/hal+json; charset=UTF-8',
]);
$apiAddress = 'http://my.project.tld/categories';
$request->setUri($apiAddress);
$request->setMethod('GET');
$client = new Client();
$response = $client->dispatch($request);
$data = $response->getContent();
... and get a broken JSON like this:
1f9e <-- What is it?
{"_links...
\u043 <-- What is it?
1a6...
tfoli <-- What is it?
0
The string is separaten into five lines:
1st line: only 1f9e
2nd line: first content part
3d line: string 1a6
4th line: the second content part
5th line: 0
Why am I getting additional symbols/strings? How to avoid this a get valid JSON output?
The problem with the getContent() method of the response object. It may not decode the content it gets in it from the request. Please have a look at here. This might be the reason. I may be wrong!
So the getBody() method of it does the decoding job for the content of the request. So please use this method instead of getContent().
$data = $response->getBody();
Hope this would help you!

Mock Slim endpoint POST requests with PHPUnit

I want to test the endpoints of my Slim application with PHPUnit. I'm struggling to mock POST requests, as the request body is always empty.
I've tried the approach as described here: Slim Framework endpoint unit testing. (adding the environment variable slim-input)
I've tried writing to php://input directly, but I've found out php://input is read only (the hard way)
The emulation of the environment works correctly as for example the REQUEST_URI is always as expected. I've found out that the body of the request is read out in Slim\Http\RequestBody from php://input.
Notes:
I want to avoid calling the controller methods directly, so I can test everything, including endpoints.
I want to avoid guzzle because it sends an actual request. I do not want to have a server running while testing the application.
my test code so far:
//inherits from Slim/App
$this->app = new SyncApiApp();
// write json to //temp, does not work
$tmp_handle = fopen('php://temp', 'w+');
fwrite($tmp_handle, $json);
rewind($tmp_handle);
fclose($tmp_handle);
//override environment
$this->app->container["environment"] =
Environment::mock(
[
'REQUEST_METHOD' => 'POST',
'REQUEST_URI' => '/1.0/' . $relativeLink,
'slim.input' => $json,
'SERVER_NAME' => 'localhost',
'CONTENT_TYPE' => 'application/json;charset=utf8'
]
);
//run the application
$response = $this->app->run();
//result: the correct endpoint is reached, but $request->getBody() is empty
Whole project (be aware that I've simplified the code on stackoverflow):
https://github.com/famoser/SyncApi/blob/master/Famoser.SyncApi.Webpage/tests/Famoser/SyncApi/Tests/
Note 2:
I've asked at the slimframework forum, link:
http://discourse.slimframework.com/t/mock-slim-endpoint-post-requests-with-phpunit/973. I'll keep both stackoverflow and discourse.slimframework up to date what is happening.
Note 3:
There is a currently open pull request of mine for this feature: https://github.com/slimphp/Slim/pull/2086
There was help over at http://discourse.slimframework.com/t/mock-slim-endpoint-post-requests-with-phpunit/973/7, the solution was to create the Request from scratch, and write to the request body.
//setup environment vals to create request
$env = Environment::mock();
$uri = Uri::createFromString('/1.0/' . $relativeLink);
$headers = Headers::createFromEnvironment($env);
$cookies = [];
$serverParams = $env->all();
$body = new RequestBody();
$uploadedFiles = UploadedFile::createFromEnvironment($env);
$request = new Request('POST', $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles);
//write request data
$request->write(json_encode([ 'key' => 'val' ]));
$request->getBody()->rewind();
//set method & content type
$request = $request->withHeader('Content-Type', 'application/json');
$request = $request->withMethod('POST');
//execute request
$app = new App();
$resOut = $app($request, new Response());
$resOut->getBody()->rewind();
$this->assertEquals('full response text', $resOut->getBody()->getContents());
The original blog post which helped to answer was at http://glenneggleton.com/page/slim-unit-testing

Guzzle encodes body to Json, but I want form data

I'm tyring to post something to Googles oAuth server to do some authentication. Google really wants this information in form data (Content-Type: application/x-www-form-urlencoded) but my guzzle client seems to insist (as far as I can tell) on making the body JSON. I'm using Guzzle 4.*
I've changed my URL to a PostCatcher.io url, so I can see what comes out (because for the life of my I can't figure out how to see the actual raw HTTP request that guzzle spits out), and it looks like theres JSON coming out.
My code (I'm using a test url by now):
$client = new GuzzleClient();
$url = "https://www.googleapis.com/" . "oauth2/v3/token";
$test_url = 'http://postcatcher.in/catchers/55602457b92ce203000032ae';
$request = $client->createRequest('POST', $test_url);
$request->setHeader('Content-Type', 'application/x-www-form-urlencoded'); //should be redundant
$body = $request->getBody();
$body->setField('code', $code);
$body->setField('client_id', $this->client_id);
$body->setField('client_secret', $this->client_secret);
$body->setField('redirect_url', $this->redicrect_url);
$body->setField('grant_type', $this->grant_type);
try {
$response = $client->send($request);
$result = $response->getBody();
return $result;
} catch (\Exception $exc) {
return $exc->getCode() . ' ' . $exc->getMessage();
}
The documentation says this should be enough. What am I missing ?
Using Guzzle 5.2 I've done the same thing with:
$request = $this->createRequest(
$method,
$uri,
['body' => $php_array_of_values ]
);
$response = $this->send($request);
I found the problem. I did upgrade to Guzzle 5.* tho I suspect that wasn't actually the solution.
I just neede to look at the response content. So in the exception clause of the call I added:
if ($exc->hasResponse()) {
return $exc->getResponse()->json();
}
Which gave me a clear error message from Google. The error message was "Missing parameter: redirect_uri". And that's because I had written url instead of uri.
Fixed that, and now it's all good.

How do you pass Content-Type header when testing Silex REST service?

I'm testing a Silex REST service as explained here but also trying to automatically decode JSON data as is also explained in the manual but somehow it fails to create the $data parameter.
In my test I'm calling the service with:
$data = file_get_contents(__DIR__.'/resources/billing-info.json');
$client->request('POST', '/users/test_user/bills',array(), array(), array('Content-Type' => 'application/json'), $data);
and in the Controller I try to access the unmarshalled data as
$app->post('/users/{username}/bills', function(Request $request, $username) use($app) {
try {
$myData = $request->data;
.....
} catch (Exception $e){
return $app->json(array('error'=>$e->getMessage()),$e->getCode());
}
});
But the $data is non existent. What am I doing wrong?
You need to change Content-Type to CONTENT_TYPE. If you look at the source code for the Client class, you'll find that the $server argument needs to match the keys given by the $_SERVER superglobal. The content-type header is stored in the CONTENT_TYPE key.
$client->request('POST', '/users/test_user/bills',array(), array(), array('CONTENT_TYPE' => 'application/json'), $data);
Check out the Documentation on the Request-Object. I guess instead of $myData = $request->data; it must be:
$myData = $request->getContent();

Categories