Guzzle pool in PHP application - php

I am trying to use Guzzle pool in PHP. But I am having difficulty in dealing with ASYNC request. Below is the code snippet.
$client = new \GuzzleHttp\Client();
function test()
{
$client = new \GuzzleHttp\Client();
$request = $client->createRequest('GET', 'http://l/?n=0', ['future' => true]);
$client->send($request)->then(function ($response) {
//echo 'Got a response! ' . $response;
return "\n".$response->getBody();
});
}
$res = test();
var_dump($res); // echoes null - I know why it does so but how to resolve the issue.
Does anybody how can I make function wait and get the correct result.

If you could return it it wouldn't be async in code style. Return the promise and unwrap it on the outside.
function test()
{
$client = new \GuzzleHttp\Client();
$request = $client->createRequest('GET', 'http://l/?n=0', ['future' => true]);
// note the return
return $client->send($request)->then(function ($response) {
//echo 'Got a response! ' . $response;
return "\n".$response->getBody();
});
}
test()->then(function($body){
echo $body; // access body here inside `then`
});

One other example I wanted to share using guzzle 6, postAsync and Pool.
function postInBulk($inputs)
{
$client = new Client([
'base_uri' => 'https://a.b.com'
]);
$headers = [
'Authorization' => 'Bearer token_from_directus_user'
];
$requests = function ($a) use ($client, $headers) {
for ($i = 0; $i < count($a); $i++) {
yield function() use ($client, $headers) {
return $client->postAsync('https://a.com/project/items/collection', [
'headers' => $headers,
'json' => [
"snippet" => "snippet",
"rank" => "1",
"status" => "published"
]
]);
};
}
};
$pool = new Pool($client, $requests($inputs),[
'concurrency' => 5,
'fulfilled' => function (Response $response, $index) {
// this is delivered each successful response
},
'rejected' => function (RequestException $reason, $index) {
// this is delivered each failed request
},
]);
$pool->promise()->wait();
}

Related

Automatically generate/call new jwt token if token expires php slim 4

I really appreciate it if someone can help me here. Is there a way I can call the endpoint that generates the api jwt token within my container, anytime the last one expires? below is auth part of my container
App::class => function (ContainerInterface $container) {
AppFactory::setContainer($container);
$app = AppFactory::create();
$app->add(new Tuupola\Middleware\JwtAuthentication([
"secret" => $_ENV['JWT_SECRET'],
"ignore" => ["/api/token","/users"], //s
"error" => function ($response, $arguments) {
$data["status"] = "error";
$data["message"] = $arguments["message"];
//$app->post('/api/token', \App\Action\ApiAuthAction::class)->setName('user-api');
return $response
->withHeader("Content-Type", "application/json")
->getBody()->write((string)json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
]));
return $app;
},
This is my auth file
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, array $args = []): ResponseInterface
{
$userData = $this->userReader->findUserByEmail($request->getParsedBody());
if ($userData) {
$now = new DateTime();
$future = new DateTime($_ENV['JWT_EXPAIRED'] . " minutes");
$jti = (new Base62)->encode(random_bytes(16));
$payload = [
"iat" => $now->getTimeStamp(),
"exp" => $future->getTimeStamp(),
"jti" => $jti,
"sub" => $userData->email
];
$secret = $_ENV['JWT_SECRET'];
$token = JWT::encode($payload, $secret, "HS256");
$data["token"] = $token;
$data["expires"] = $future->getTimeStamp();
$response->getBody()->write((string)json_encode([
'success' => true,
'message' => $token
]));
} else {
$response->getBody()->write((string)json_encode([
'success' => false,
'message' => 'Invalid Email or Password'
]));
}
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
}

Using guzzle to perform batch requests

i'm working on a project where i need to perform 2000 asynchronous requests using Guzzle to an endpoint and each time i need to change the ID in the url.
the endpoint looks like this: https://jsonplaceholder.typicode.com/posts/X
I tried to use a for loop to do that the only issue is that it's not asynchronous. what's the more efficient way to do such task?
use GuzzleHttp\Client;
public function fetchPosts () {
$client = new Client();
$posts = [];
for ($i=1; $i < 2000; $i++) {
$response = $client->post('https://jsonplaceholder.typicode.com/posts/' . $i);
array_push($posts, $response->getBody());
}
return $posts;
}
You can try this,
public function fetchBooks()
{
$results = [];
$client = new \GuzzleHttp\Client([
'base_uri' => 'https://jsonplaceholder.typicode.com'
]);
$headers = [
'Content-type' => 'application/json; charset=UTF-8'
];
$requests = function () use ($client,$headers) {
for ($i = 1; $i < 7; $i++) {
yield function() use ($client, $headers,$i) {
return $client->postAsync('/posts',[
'headers' => $headers,
'json' => [
'title' => 'foonov2020',
'body' => 'barfoonov2020',
'userId' => $i,
]
]);
};
}
};
$pool = new \GuzzleHttp\Pool($client, $requests(),[
'concurrency' => 5,
'fulfilled' => function (Response $response, $index) use (&$results) {
$results[] = json_decode($response->getBody(), true);
},
'rejected' => function (\GuzzleHttp\Exception\RequestException $reason, $index) {
throw new \Exception(print_r($reason->getBody()));
},
]);
$pool->promise()->wait();
return response()->json($results);
}
It will give you output,

How to send form fields with Guzzle 6?

I am developing my unit tests for an API created in Symfony4
Reading the Guzzle documentation I generated the following code:
File SecurityControllerTest.php
$client = new Client([
'base_uri' => 'http://localhost/sacrepad/sacrepad-api/public/index.php/',
'timeout' => 2.0,
]);
$data = array();
$data['email'] = 'admin#admin.com';
$data['password'] = '12345678';
$data2 = array();
$data2['json'] = $data;
$formData = json_encode($data);
$response = $client->request('POST', 'login', [
'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
'form_params' => [
'json' => $formData,
]
]);
$body = json_decode($response->getBody(), true);
File SecurityController.php
/**
* #Route("/login", name="login", methods={"POST"})
*/
public function login(Request $request,Helpers $helpers,ValidatorInterface $validator, JwtAuth $jwtauth) {
$data = array(
'status' => 'error',
'code' => 400,
'msg' => 'data not received'
);
$json = $request->request->get('json');
$params = json_decode($json);
}
When I run the tests with the phpunit command, I get the following error:
1) App\Tests\SecurityControllerTest::testAuth GuzzleHttp\Exception\ServerException: Server error: `POST http://localhost/sacrepad/sacrepad-api/public/index.php/login` resulted in a `500 Internal Server Error` response:
If I change the name of the request:
$json = $request->request->get('json2');
It works and it returns me the following:
array(3) {
["status"]=>
string(5) "error"
["code"]=>
int(400)
["msg"]=>
string(18) "data not received"
}
Any ideas on how to make it work and send the parameters?
i build a class for working with guzzle
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
class Api
{
protected $client;
protected $url;
public function __construct()
{
$this->client = new Client([
'verify'=>false
]);
$this->url = 'http://localhost/sacrepad/sacrepad-api/public/';
}
public function get($endpoint, $params = [], $headers = [])
{
$response = $this->sendRequest(
'GET',
$this->url . $endpoint,
$params,
$headers
);
return $response;
}
public function post($endpoint, $params = [], $headers = [])
{
$response = $this->sendRequest(
'POST',
$this->url . $endpoint,
$params,
$headers
);
return $response;
}
public function sendRequest($type, $url, $params = [], $headers = [])
{
if ($type == 'GET') {
$data = [
'query' => $params
];
} elseif ($type == 'FILE') {
$type = 'POST';
$data = [
'multipart' => $params // TODO implements later
];
} else {
$data = [
'json' => $params
];
}
if (!empty($headers)) {
$data['headers'] = $headers;
}
$data['headers']['X-REAL-IP'] = $_SERVER['REMOTE_ADDR'];
$data['headers']['User-Agent'] = $_SERVER['HTTP_USER_AGENT'];;
$data['headers']['X-Platform'] = 'web';
try {
$response = $this->client->request(
$type,
$url,
$data
);
if (in_array($response->getStatusCode(), ['200', '403', '404'])) {
return json_decode($response->getBody());
}
return false;
} catch (RequestException $re) {
if (in_array($re->getResponse()->getStatusCode(), ['403', '404', '422'])) {
return json_decode($re->getResponse()->getBody());
}
return json_decode($re->getResponse()->getBody());
} catch (Exception $e) {
return false;
}
}
}
when i want to send a post request it would be like this
$response = (new Api())->post('index.php/',[
'email'=> 'admin#admin.com',
'password' => '123456'
]);
now it will send a post request to index.php and send email and password data i hope it would be helpful

Can I call a method from the same method?

I'm in a situation where I need to call the same method if any exception is thrown to ensure I'm not duplicating any code. However, it's not working as I thought. Here's the relevant code:
public static function getFolderObject($folder_id)
{
$client = new Client('https://api.box.com/{version}/folders', [
'version' => '2.0',
'request.options' => [
'headers' => [
'Authorization' => 'Bearer ' . self::getAccessToken(),
]
]
]);
$request = $client->get($folder_id);
try {
$response = $request->send();
$result = $response->json();
$files = $result['item_collection']['entries'];
} catch (BadResponseException $e) {
$result = $e->getResponse()->getStatusCode();
if ($result === 401) {
self::regenerateAccessToken();
self::getFolderObject();
}
}
return count($files) ? $files : false;
}
As you can see I'm calling the method from the method method under the if condition self::getFolderObject(); to prevent duplicate code again in under the if statement from beginning of the method. However, if I duplicate the code it works as expected. Is there any solution to achieve what I want?
You have missed to return the value and assign the folder_id:
public static function getFolderObject($folder_id)
{
$client = new Client('https://api.box.com/{version}/folders', [
'version' => '2.0',
'request.options' => [
'headers' => [
'Authorization' => 'Bearer ' . self::getAccessToken(),
]
]
]);
$request = $client->get($folder_id);
try {
$response = $request->send();
$result = $response->json();
$files = $result['item_collection']['entries'];
} catch (BadResponseException $e) {
$result = $e->getResponse()->getStatusCode();
if ($result === 401) {
self::regenerateAccessToken();
return self::getFolderObject($folder_id);
}
}
return count($files) ? $files : false;
}

how to post Guzzle 6 Async data

i trying to post data as Async with using Guzzle 6(latest ver)
$client = new Client();
$request = $client->postAsync($url, [
'json' => [
'company_name' => 'update Name'
],
]);
but i am not getting any request form Guzzle like post request on terminal
Because it's a promise, you need to put then
and the promise will not called unless you put $promise->wait()
This is a simple post request using postAsync based on your question:
$client = new Client();
$promise = $client->postAsync($url, [
'json' => [
'company_name' => 'update Name'
],
])->then(
function (ResponseInterface $res){
$response = json_decode($res->getBody()->getContents());
return $response;
},
function (RequestException $e) {
$response = [];
$response->data = $e->getMessage();
return $response;
}
);
$response = $promise->wait();
echo json_encode($response);
Have you tried to send the request?
http://guzzle.readthedocs.org/en/latest/index.html?highlight=async
$client = new Client();
$request = new Request('POST', $url, [
"json" => [
'company_name' => 'update Name']
]);
$promise = $client->sendAsync($request)->then(function ($response) {
echo 'I completed! ' . $response->getBody();
});
$promise->wait();
Guzzle 6 has very little practical examples/documentation for developers to refer. I am sharing an example on how to use postAsync and Pool object. This allows concurrent async requests using guzzle 6. ( I was not able to find a direct example and spent 2 days to get working code )
function postInBulk($inputs)
{
$client = new Client([
'base_uri' => 'https://a.b.com'
]);
$headers = [
'Authorization' => 'Bearer token_from_directus_user'
];
$requests = function ($a) use ($client, $headers) {
for ($i = 0; $i < count($a); $i++) {
yield function() use ($client, $headers) {
return $client->postAsync('https://a.com/project/items/collection', [
'headers' => $headers,
'json' => [
"snippet" => "snippet",
"rank" => "1",
"status" => "published"
]
]);
};
}
};
$pool = new Pool($client, $requests($inputs),[
'concurrency' => 5,
'fulfilled' => function (Response $response, $index) {
// this is delivered each successful response
},
'rejected' => function (RequestException $reason, $index) {
// this is delivered each failed request
},
]);
$pool->promise()->wait();
}
most likely you'll need to call wait();
$request->wait();

Categories