Is there a way to globally add form_params to all requests with guzzle 6?
For example:
$client = new \GuzzleHttp\Client([
'global_form_params' => [ // This isn't a real parameter
'XDEBUG_SESSION_START' => '11845',
'user_token' => '12345abc',
]
]);
$client->post('/some/web/api', [
'form_params' => [
'some_parameter' => 'some value'
]
]);
In my ideal world, the post would have the result of array_merge-ing global_form_params and form_params:
[
'XDEBUG_SESSION_START' => '11845',
'user_token' => '12345abc',
'some_parameter' => 'some value',
]
I can see also wanting something like this for query or json
According to Creating a client you can set "any number of default request options" and on the GuzzleHttp\Client Source Code
$client = new Client['form_params' => [form values],]);
would apply your form_params to every request.
This could create issues with GET requests due to the Content-Type header being changed within Client::applyOptions. It would ultimately depend on server configuration.
If your intentions are to have the client make both GET and POST requests then you might be better served by moving the form_params into middleware. For example:
$stack->push(\GuzzleHttp\Middleware::mapRequest(function (RequestInterface $request) {
if ('POST' !== $request->getMethod()) {
// pass the request on through the middleware stack as-is
return $request;
}
// add the form-params to all post requests.
return new GuzzleHttp\Psr7\Request(
$request->getMethod(),
$request->getUri(),
$request->getHeaders() + ['Content-Type' => 'application/x-www-form-urlencoded'],
GuzzleHttp\Psr7\stream_for($request->getBody() . '&' . http_build_query($default_params_array)),
$request->getProtocolVersion()
);
});
Related
I use Guzzle6 in the PSR7 flavor because it integrates nicely with Hawk authentication. Now, I face problems adding a body to the request.
private function makeApiRequest(Instructor $instructor): ResponseInterface
{
$startDate = (new CarbonImmutable('00:00:00'))->toIso8601ZuluString();
$endDate = (new CarbonImmutable('00:00:00'))->addMonths(6)->toIso8601ZuluString();
$instructorEmail = $instructor->getEmail();
$body = [
'skip' => 0,
'limit' => 0,
'filter' => [
'assignedTo:user._id' => ['email' => $instructorEmail],
'start' => ['$gte' => $startDate],
'end' => ['$lte' => $endDate],
],
'relations' => ['reasonId']
];
$request = $this->messageFactory->createRequest(
'POST',
'https://app.absence.io/api/v2/absences',
[
'content_type' => 'application/json'
],
json_encode($body)
);
$authentication = new HawkAuthentication();
$request = $authentication->authenticate($request);
return $this->client->sendRequest($request);
}
When I var_dump the $request variable, I see no body inside the request. This is backed by the fact that the API responds as if no body was sent. I cross-checked this in Postman. As you can see, the body specifies filters and pagination, so it is easy to see that the results I get are actually not filtered.
The same request in Postman (with body) works flawlessly.
As the parameter be can of type StreamInterface I created a stream instead and passed the body to it. Didn't work either.
Simple JSON requests can be created without using json_encode()... see the documentation.
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://app.absence.io/api/v2',
'timeout' => 2.0
]);
$response = $client->request('POST', '/absences', ['json' => $body]);
Found the problem, actually my POST body is NOT empty. It just turns out that dumping the Request will not hint anything about the actual body being enclosed in the message.
I can recommend anyone having similar problems to use http://httpbin.org/#/HTTP_Methods/post_post to debug the POST body.
Finally, the problem was that my content_type header spelling was wrong as the server expects a header Content-Type. Because of this, the JSON data was sent as form data.
I am using "guzzlehttp/guzzle": "~6.0", and trying to post a file to an API endpoint. The file posts fine when using RequestBin but the API is not getting the header it requires. The Header is not sent to Request bin either. According to the docs, I need to do an array of associative arrays. http://docs.guzzlephp.org/en/latest/quickstart.html#post-form-requests
However, this is not working. Here's the Guzzle request:
$client = new GuzzleHttp\Client(['base_uri' => '127.0.0.1:3000']);
$response = $client->request('POST', '/process', [
'multipart' => [
[
'name' => 'file',
'contents' => $file,
'bucketName' => 'test',
'headers' => ['X-API-Key' => 'abc345']
],
]
]);
What am I doing wrong that it's not sending the header?
Thank you very much,
Josh
Headers is an $option, that's mean it must be at the same level as multipart.
<?php
$response = $client->request('POST', '/process', [
'multipart' => [
[
'name' => 'file',
'contents' => 'test',
'bucketName' => 'test',
],
],
'headers' => ['X-API-Key' => 'abc345'] // <------- HERE
]);
You were probably using multipart in conjunction with form_params , this is not explicitly explained in the documentation of laravel but guzzle won't work with both
Note
multipart cannot be used with the form_params option. You will need to
use one or the other. Use form_params for
application/x-www-form-urlencoded requests, and multipart for
multipart/form-data requests.
This option cannot be used with body, form_params, or json
To solve this problem you will need to parse all the params to multipart, if you are using laravel or lumen you can do it in this way
if(!empty($this->files))
{
//if there is an image parse all the rest parameters to
// multipart
$file_keys=array_keys($this->files);
foreach($this->files as $k => $file)
{
$http = $http->attach($k, file_get_contents($file),$k);
}
foreach($this->data as $dk =>&$d)
{
if(!in_array($dk,$file_keys))
{
if(is_array($d))
{
$d=json_encode($d);
}
$http = $http->attach($dk,$d);
}
}
return $http=$http->post($this->url);
}
//if there isn't any file just send all as form_params
return $http=$http->post($this->url,$this->data);
My objective is to use Guzzle 6 to create a pool of asynchronous requests that PUT json data. Then monitor each $promise success/failure.
For comparison to my POOL code example, the following single request to $client->request() converts the 3rd parameter to encoded json and then adds the Content-type:application/json.**
$client = new Client([
'base_uri' => BASE_URL . 'test/async/', // Base URI is used with relative requests
'timeout' => 0, // 0 no timeout for operations and watching Promises
]);
$response = $client->request('PUT', 'cool', ['json' => ['foo' => 'bar']]);
On the receiving API endpoint, I can read the json from the single request above by doing the following:
$json = file_get_contents('php://input');
$json = json_decode($json, true);
Using the concurrent requests example in the docs, for creating a Pool of asynchronous requests using new Request(), I hoped the same parameters (method, url endpoint, json flag) could be used, as in the single $client->request() example above. However, yield new Request() does not handle the 3rd json parameter like $client->request(). What is the correct Guzzle function to call from my Pool code to set json and content-type correctly? Or is there a better way to create a large pool of asynchronous requests and monitor their outcome?
POOL code example:
$this->asyncRequests = [
[
'endpoint' => 'cool'
],
[
'endpoint' => 'awesome'
],
[
'endpoint' => 'crazy'
],
[
'endpoint' => 'weird'
]
];
$client = new Client([
'base_uri' => BASE_URL, // Base URI is used with relative requests
'timeout' => 0 // 0 no timeout for operations and watching Promises
]);
$requests = function ($asyncRequests) {
$uri = BASE_URL . 'test/async/';
foreach ($asyncRequests as $key => $data) {
yield new Request('PUT', "{$uri}{$data['endpoint']}", ['json' => ['foo' => 'bar']]);
}
};
$pool = new Pool($client, $requests($this->asyncRequests), [
'concurrency' => 10,
'fulfilled' => function ($response, $index) {
$this->handleSuccessPromises($response, $index);
},
'rejected' => function ($reason, $index) {
$this->handleFailurePromises($reason, $index);
},
]);
$promise = $pool->promise(); // Initiate the transfers and create a promise
$promise->wait(); // Force the pool of requests to complete.
Hopefully, someone else will jump in and let me know if there is a more correct way to accomplish my objective, but after looking under the hood in Guzzle I realized new Request()'s 3rd parameter was looking for header information, and the 4th parameter was looking for a body. So the following code works using the Pool.:
foreach ($syncRequests as $key => $headers) {
yield new Request('PUT', "{$uri}{$headers['endpoint']}", ['Content-type' => 'application/json'], json_encode(['json' => ['nonce' => $headers['json']]]));
}
Also in docs for Psr7\Request
If you want full control, don't use the Request() object in your Pool. Instead, start the request yourself by having your pool's generator yielding a callable function which starts the request. That gives you total control of all options. Here is a correct code example:
https://stackoverflow.com/a/40622269/5562035
I am using Guzzle (v6.1.1) in PHP to make a POST request to a server. It works fine. I am adding some logging functions to log what was sent and received and I can't figure out how to get the data that Guzzle sent to the server. I can get the response just fine, but how do I get the sent data? (Which would be the JSON string.)
Here is the relevant portion of my code:
$client = new GuzzleHttp\Client(['base_uri' => $serviceUrlPayments ]);
try {
$response = $client->request('POST', 'Charge', [
'auth' => [$securenetId, $secureKey],
'json' => [ "amount" => $amount,
"paymentVaultToken" => array(
"customerId" => $customerId,
"paymentMethodId" => $token,
"publicKey" => $publicKey
),
"extendedInformation" => array(
"typeOfGoods" => $typeOfGoods,
"userDefinedFields" => $udfs,
"notes" => $Notes
),
'developerApplication'=> $developerApplication
]
]);
} catch (ServerErrorResponseException $e) {
echo (string) $e->getResponse()->getBody();
}
echo $response->getBody(); // THIS CORRECTLY SHOWS THE SERVER RESPONSE
echo $client->getBody(); // This doesn't work
echo $client->request->getBody(); // nor does this
Any help would be appreciated. I did try to look in Guzzle sourcecode for a function similar to getBody() that would work with the request, but I'm not a PHP expert so I didn't come up with anything helpful. I also searched Google a lot but found only people talking about getting the response back from the server, which I have no trouble with.
You can do this work by creating a Middleware.
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
$stack = HandlerStack::create();
// my middleware
$stack->push(Middleware::mapRequest(function (RequestInterface $request) {
$contentsRequest = (string) $request->getBody();
//var_dump($contentsRequest);
return $request;
}));
$client = new Client([
'base_uri' => 'http://www.example.com/api/',
'handler' => $stack
]);
$response = $client->request('POST', 'itemupdate', [
'auth' => [$username, $password],
'json' => [
"key" => "value",
"key2" => "value",
]
]);
This, however, is triggered before to receive the response. You may want to do something like this:
$stack->push(function (callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
return $handler($request, $options)->then(
function ($response) use ($request) {
// work here
$contentsRequest = (string) $request->getBody();
//var_dump($contentsRequest);
return $response;
}
);
};
});
Using Guzzle 6.2.
I've been struggling with this for the last couple days too, while trying to build a method for auditing HTTP interactions with different APIs. The solution in my case was to simply rewind the request body.
The the request's body is actually implemented as a stream. So when the request is sent, Guzzle reads from the stream. Reading the complete stream moves the stream's internal pointer to the end. So when you call getContents() after the request has been made, the internal pointer is already at the end of the stream and returns nothing.
The solution? Rewind the pointer to the beginning and read the stream again.
<?php
// ...
$body = $request->getBody();
echo $body->getContents(); // -->nothing
// Rewind the stream
$body->rewind();
echo $body->getContents(); // -->The request body :)
My solution for Laravel from 5.7:
MessageFormatter works with variable substitutions, see this: https://github.com/guzzle/guzzle/blob/master/src/MessageFormatter.php
$stack = HandlerStack::create();
$stack->push(
Middleware::log(
Log::channel('single'),
new MessageFormatter('Req Body: {request}')
)
);
$client = new Client();
$response = $client->request(
'POST',
'https://url.com/go',
[
'headers' => [
"Content-Type" => "application/json",
'Authorization' => 'Bearer 123'
],
'json' => $menu,
'handler' => $stack
]
);
You can reproduce the data string created by the request by doing
$data = array(
"key" => "value",
"key2" => "value",
);
$response = $client->request('POST', 'itemupdate', [
'auth' => [$username, $password],
'json' => $data,
]);
// ...
echo json_encode($data);
This will output your data as JSON string.
Documentation at http://php.net/manual/fr/function.json-encode.php
EDIT
Guzzle has a Request and a Response class (and many other).
Request has effectively a getQuery() method that returns an object containing your data as private, same as all other members.
Also you cannot access it.
This is why I think manually encode it is the easier solution.
If you want know what is done by Guzzle, it also have a Collection class that transform data and send it in request.
I am trying to test my web service which is written in laravel 5 using phpunit. I am new to this testing framework. The application uses JWT for authentication which is passed in headers in the requests.
My test look likes so:
$response = $this->call('POST', 'authenticate', [
'username' => 'carparts',
'email' => 'admin#admin.com',
'password' => 'password'
]);
$token = 'Bearer ' . json_decode($response->getContent())->token;
$response = $this->call('POST', 'users', [
"first_name" => "Test",
"last_name" => "Test",
"email" => 'test#test.com',
"password" => "testing",
"role" => "1"
], [], [], [
'Authorization' => $token
]);
dd($response->getContent());
The token is returned fine but when I try to use in the next request to create a new user, it fails saying the token could not parsed from the request. When I do request()->headers in a middleware to check, even that does not show the header. What am I doing wrong? How to pass headers in a request using PHPUnit?
There's a function to translate headers to server vars. Try passing this instead:
$this->transformHeadersToServerVars([ 'Authorization' => $token ])
$server argument is an array of server variables, not http headers.
Pass your token as HTTP_AUTHORIZATION:
$response = $this->call('POST', 'users', [
"first_name" => "Test",
"last_name" => "Test",
"email" => 'test#test.com',
"password" => "testing",
"role" => "1"
], [], [], [
'HTTP_AUTHORIZATION' => $token
]);
just a wild guess...
check your .htaccess, it must contain
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
I spent a day digging this problem.
What really happens when you run tests: you run phpunit command and then every request doesn't really sends, it's just run in current instance. It means that calling $this->call() doesn't invoke http request.
Every test case (i.e. test method) calls setUp method which runs createApplication method. All methods required by the kernel for booting up an application run in method setUp() which calls exactly before your test case:
// Middleware
...
// There is nothing related to testCase
function testCase() {
// set header
}
// Action
...
// You can use header here
To solve this problem I add method exact before my test case. I still can't change headers but I don't really care. To get custom information I do the following:
// in tests:
function _setHeader() {
global $_TESTING_X_HEADER;
$_TESTING_X_HEADER = "some-value";
}
function testCase() {
// setUp will run with $_TESTING_X_HEADER == "some-value"
// test goes here
}
// in middleware:
if(App::environment('testing')) {
$h = global $_TESTING_X_HEADER;
} else {
$h = request()->header('x-header');
}
I'm using PHP unit to do some functional tests.
$client->request('GET','url', [], [], ['HTTP_Authorization' => $token]);
It works for me !