I'm trying to send a request to an endpoint, but I don't want to wait for them to respond, as I don't need the response. So I'm using Guzzle, here's how:
$url = 'http://example.com';
$client = new \Guzzelhttp\Client();
$promise = $client->postAsync($url, [
'headers' => ['Some headers and authorization'],
'query' => [
'params' => 'params',
]
])->then(function ($result) {
// I don't need the result. So I just leave it here.
});
$promise->wait();
A I understood, I have to call the wait method on the client in order to actually send the request. But it's totally negates the request being "async" because if the url was not accessible or the server was down, the application would wait for a timeout or any other errors.
So, the question here is, what does Guzzle mean by "async" when you have to wait for the response anyway? And how can I call a truly async request with PHP?
Thanks
What you can do is:
$url = 'http://example.com';
$client = new \Guzzelhttp\Client();
$promise = $client->postAsync($url, [
'headers' => ['Some headers and authorization'],
'query' => [
'params' => 'params',
]
])->then(function ($result) {
return $result->getStatusCode();
})
->wait();
echo $promise;
You need the wait() to be called as the last line so you get the result which will come from your promise.
In this case it will return just the status code.
Just as mentioned in Github is not able to "fire and forget"so i think what you are trying to achieve, like a complete promise like in Vue or React won't work for you here the way you want it to work.
Another approach and what i do personally is to use a try-catch on guzzle requests, so if there is a guzzle error then you catch it and throw an exception.
Call then() method if you don't want to wait for the result:
$client = new GuzzleClient();
$promise = $client->getAsync($url)
$promise->then();
Empty then() call will make an HTTP request without waiting for result, Very similar to
curl_setopt(CURLOPT_RETURNTRANSFER,false)
use Illuminate\Support\Facades\Http;
...Some Code here
$prom = Http::timeout(1)->async()->post($URL_STRING, $ARRAY_DATA)->wait();
... Some more important code here
return "Request sent"; //OR whatever you need to return
This works for me as I don't need to know the response always.
It still uses wait() but because of the small timeout value it doesn't truly wait for the response.
Hope this helps others.
Related
i'm using PHP with Guzzle.
I have this code:
$client = new Client();
$request = new \GuzzleHttp\Psr7\Request('POST', 'http://localhost/async-post/tester.php',[
'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
'form_params' => [
'action' => 'TestFunction'
],
]);
$promise = $client->sendAsync($request)->then(function ($response) {
echo 'I completed! ' . $response->getBody();
});
$promise->wait();
For some reason Guzzle Doesn't send the POST Parameters.
Any suggestion?
Thanks :)
I see 2 things.
The parameters have to go as string (json_encode)
And you were also including them as part of the HEADER, not the BODY.
Then i add a function to handle the response as ResponseInterface
$client = new Client();
$request = new Request('POST', 'https://google.com', ['Content-Type' => 'application/x-www-form-urlencoded'], json_encode(['form_params' => ['s' => 'abc',] ]));
/** #var Promise\PromiseInterface $response */
$response = $client->sendAsync($request);
$response->then(
function (ResponseInterface $res) {
echo $res->getStatusCode() . "\n";
},
function (RequestException $e) {
echo $e->getMessage() . "\n";
echo $e->getRequest()->getMethod();
}
);
$response->wait();
In this test Google responds with a
Client error: POST https://google.com resulted in a 405 Method Not Allowed
But is ok. Google doesn't accepts request like this.
Guzzle isn't truely asynchronous. It's more of multi-threading. That is why you have the wait() line to prevent the your current PHP script from closing until all multiple spun threads finish. If you remove the wait() line, the PHP process spun by the script ends immediately with all it's threads and your request is never sent.
Ergo, you need Guzzle (and Curl) for multi-processing(concurrent) I/O and not for asynchronous I/O. In your case, you are processing one request and Guzzle promises are simply an overkill.
To send a request with Guzzle, simply do this:
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;
$client = new Client();
$header = ['Content-Type' => 'application/x-www-form-urlencoded'];
$body = json_encode(['id' => '2', 'name' => 'dan']);
$request = new Request('POST', 'http://localhost/async-post/tester.php', $header, $body);
$response = $client->send($request);
Also, it seems you are using the form action attribute rather than the actual form data in form-params.
I'm posting this answer because I tried to achieve something really asynchronous with php - Schedule I/O processing as a background task, continue processing script and serve the page; I/O continues in background and completes without disrupting the client. Laravel Queues was the best thing I could find.
I am using Guzzle to consume a SOAP API. I have to make 6 requests, but in the future this might be even an indeterminate amount of requests.
Problem is that the requests are being send sync, instead of async. Every request on it's own takes +-2.5s. When I send all 6 requests paralell (at least thats what I am trying) it takes +- 15s..
I tried all the examples on Guzzle, the one with a fixed array with $promises, and even the pool (which I need eventually). When I put everything in 1 file (functional) I manage to get the total timing back to 5-6s (which is OK right?). But when I put everything in Objects and functions somehow I do something that makes Guzzle decide to do them sync again.
Checks.php:
public function request()
{
$promises = [];
$promises['requestOne'] = $this->requestOne();
$promises['requestTwo'] = $this->requestTwo();
$promises['requestThree'] = $this->requestThree();
// etc
// wait for all requests to complete
$results = \GuzzleHttp\Promise\settle($promises)->wait();
// Return results
return $results;
}
public function requestOne()
{
$promise = (new API\GetProposition())
->requestAsync();
return $promise;
}
// requestTwo, requestThree, etc
API\GetProposition.php
public function requestAsync()
{
$webservice = new Webservice();
$xmlBody = '<some-xml></some-xml>';
return $webservice->requestAsync($xmlBody, 'GetProposition');
}
Webservice.php
public function requestAsync($xmlBody, $soapAction)
{
$client = new Client([
'base_uri' => 'some_url',
'timeout' => 5.0
]);
$xml = '<soapenv:Envelope>
<soapenv:Body>
'.$xmlBody.'
</soapenv:Body>
</soapenv:Envelope>';
$promise = $client->requestAsync('POST', 'NameOfService', [
'body' => $xml,
'headers' => [
'Content-Type' => 'text/xml',
'SOAPAction' => $soapAction, // SOAP Method to post to
],
]);
return $promise;
}
I changed the XML and some of the parameters for abbreviation. The structure is like this, because I eventually have to talk against multiple API's, to thats why I have a webservice class in between that does all the preparation needed for that API. Most API's have multiple methods/actions that you can call, so that why I have something like. API\GetProposition.
Before the ->wait() statement I can see all $promises pending. So it looks like there are being send async. After ->wait() they have all been fulfilled.
It's all working, minus the performance. All 6 requests don't take more then 2.5 to max 3s.
Hope someone can help.
Nick
The problem was that the $client object was created with every request. Causing the curl multi curl not to be able to know which handler to use.
Found the answer via https://stackoverflow.com/a/46019201/7924519.
I'm trying to send post async request
$client = new Client([
'timeout' => 2.0,
]);
$request = new \GuzzleHttp\Psr7\Request('POST', 'localhost/test.php' , [ 'json' => [
"username"=>"xyz",
"password"=>"xyz",
"first_name"=>"test",
"last_name"=>"test",
"email"=>"test#test.com",
"roles"=>"Administrator"
], ]);
$promise = $client->sendAsync($request)->then(function ($response) {
echo 'I completed! ' . $response->getBody();
});
$promise->wait();
test.php code is
var_dump($_POST);
The result should be the variables that i've set, but i get empty array.
I believe the problem is that you are using "json" => [], you might have to use "form_params" => [] to get things populated to the $_POST array.
See some documentation here: http://docs.guzzlephp.org/en/stable/request-options.html#form-params
The json option is used for RESTful APIs that explicitly require json as input, but in php normally $_POST is populated with the posted form, for example the named <input>s in a <form>
Use a scheduler function like below , read the register_shutdown_function
this function will be called at end of php script, so its a good time to close the connection and continue the heavy jobs.
public static function schedulePromise($callable, ...$args)
{
register_shutdown_function(function ($callable, ...$args) {
#session_write_close();
#ignore_user_abort(true);
call_user_func($callable, ...$args);
}, $callable, ...$args);
}
And the user never feels the heavy work behind the scenes. for example waiting for request in guzzle http
$promise = $client->sendAsync($request, ['timeout' => 10])
->then(function ($response) {
// success
}, function ($response) {
// failure
});
self::schedulePromise([$promise, 'wait'],false);
How can i call methods asynchronously in symfony2.7 Like.
I have to retrieve data making 4 different API connections. The problem is slow response from my application since PHP is synchronous so it has to wait for the response from all the API and then render the data.
class MainController{
public function IndexAction(){
// make Asynchronous Calls to GetFirstAPIData(), GetSecondAPIData(), GetThridAPIData()
}
public function GetFirstAPIData(){
// Get data
}
public function GetSecondAPIData(){
// Get data
}
public function GetThridAPIData(){
// Get data
}
}
You can use guzzle for that, especially when we're are talking about http based apis. Guzzle is a web-client which has async calls built in.
The code would look somewhat like this: (taken from the docs)
$client = new Client(['base_uri' => 'http://httpbin.org/']);
// Initiate each request but do not block
$promises = [
'image' => $client->getAsync('/image'),
'png' => $client->getAsync('/image/png'),
'jpeg' => $client->getAsync('/image/jpeg'),
'webp' => $client->getAsync('/image/webp')
];
// Wait on all of the requests to complete. Throws a ConnectException
// if any of the requests fail
$results = Promise\unwrap($promises);
// Wait for the requests to complete, even if some of them fail
$results = Promise\settle($promises)->wait();
// You can access each result using the key provided to the unwrap
// function.
echo $results['image']->getHeader('Content-Length');
echo $results['png']->getHeader('Content-Length');
In this example all 4 requests are executed in parallel. Note: Only IO is async not the handling of the results. But that's probably what you want.
I asked a similar question earlier, in a nutshell I have an API application that takes json requests and outputs an json response.
For instance here is one of the requests that I need to test out, how can I use this json object with my testing to emulate a 'real request'
{
"request" : {
"model" : {
"code" : "PR92DK1Z"
}
}
The response is straightforward (this bit has been done).
From other users on here this is the optimised method using Yii to do this, I am just unsure how to emulate the json request - e.g essentially send a JSON HTTP request, can anyone assist on how to do this?
public function actionMyRequest() {
// somehow add my json request...
$requestBody = Yii::app()->request->getRawBody();
$parsedRequest = CJSON::decode($requestBody);
$code = $parsedRequest["request"]["model"]["code"];
}
I don't understand if you want your app to send an http request and get the result or at the opposite receive a http request
I answered for the first assumption, I'll change my answer if you want the other
For me the best way to send an HTTP request is to use Guzzle http client.
This is not a yii extension, but you can use third party libraries with yii.
Here's an example from Guzzle page:
$client = new GuzzleHttp\Client();
$res = $client->get('https://api.github.com/user', [
'auth' => ['user', 'pass']
]);
echo $res->getStatusCode(); // 200
echo $res->getHeader('content-type'); // 'application/json; charset=utf8'
echo $res->getBody();
So in your case you could do something like:
public function actionMyRequest() {
$client = new GuzzleHttp\Client();
$res = $client->get('https://api.your-url.com/');
$requestBody = $res->getBody();
$parsedRequest = CJSON::decode($requestBody);
$code = $parsedRequest["request"]["model"]["code"];
}