ReactPHP and Promises - php

I'm trying to understand the concept of Promises using ReactPHP
$app = function ($request, $response) use ($redis, $config) {
$promise = React\Promise\all(
array(
AsyncGetUser(),
AsyncGetDB(),
AsyncGetTemplate()
)
)->then(function ($res) {
$result = ParseTemplate($user, $template, $whatever);
}
\React\Promise\resolve($promise);
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->end($result);
}
$http->on('request', $app);
But $response is sent before $result is ready.
How can do something like await for $promise so I send $result properly ?
I've tried to move $response->end to another->then() section but then I don't get any response in browser (i.e. the script gets a result when $app = function is finished already).

I don't know reactphp at all, but if promises work like promises in JS for example, seems like you need to write the response in the ->then where you have a result!
$app = function ($request, $response) use ($redis, $config) {
$promise = React\Promise\all(
array(
AsyncGetUser(),
AsyncGetDB(),
AsyncGetTemplate()
)
)->then(function ($res) {
$result = ParseTemplate($user, $template, $whatever);
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->end($result);
}
}
$http->on('request', $app);
Note: the following line in your code
\React\Promise\resolve($promise);
makes no sense. \React\Promise\resolve doesn't "resolve the promise" as you seem to think, it creates and returns a resolved promise - which you discard!

Related

PHP - Guzzle Middleware

I'm using the Pole Emploi's API,but I encounter 401 error 25 minutes later, when my token expires.
I looked for a way to get a new token and retry the request, but no way for me to understand how Middlewares work, and if I should use a middleware for my needings.
On Guzzle's docs this is written :
Middleware functions return a function that accepts the next handler to invoke. This returned function then returns another function that acts as a composed handler-- it accepts a request and options, and returns a promise that is fulfilled with a response. Your composed middleware can modify the request, add custom request options, and modify the promise returned by the downstream handler.
And this is an example code from the docs :
use Psr\Http\Message\RequestInterface;
function my_middleware()
{
return function (callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
return $handler($request, $options);
};
};
}
So I think I need to manage the "promise" to see if its HTTP code is 401, and then get a new token and retry the request ?
I'm lost, so I would appreciate if someone can explain me the logic of this with different words maybe :)
Thank you in advance.
It doesn't need to be that difficult, add a handler that takes care of the job, in combination with cache that expires.
If you don't use cache then I guess you could probably save it to a file along with a timestamp for expiration that you check against when fetching it.
class AuthenticationHandler
{
private $username;
private $password;
private $token_name = 'access_token';
public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}
public function __invoke(callable $handler)
{
return function (RequestInterface $request, array $options) use ($handler) {
if (is_null($token = Cache::get($this->token_name))) {
$response = $this->getJWT();
Cache::put($this->token_name, $token = $response->access_token, floor($response->expires_in));
}
return $handler(
$request->withAddedHeader('Authorization', 'Bearer '.$token)
->withAddedHeader('Api-Key', $this->api_key), $options
);
};
}
private function getJWT()
{
$response = (new Client)->request('POST', 'new/token/url', [
'form_params' => [
'grant_type' => 'client_credentials',
'username' => $this->username,
'password' => $this->password,
],
]);
return json_decode($response->getBody());
}
}
Then use it:
$stack = HandlerStack::create(new CurlHandler());
$stack->push(new AuthenticationHandler('username', 'password'));
$client = new GuzzleHttp\Client([
'base_uri' => 'https://api.com',
'handler' => $stack,
]);
Now you will always have a valid token, and you will never have to worry about it ever again.
I wouldn't recommend doing this as it can become hell to debug your application and as far as I am aware Guzzle doesn't really allow access to the client from middleware. Regardless you can use Promises to get around. If I were you I would refresh token before other requests, or refresh periodically. It might be fine if you are firing requests one by one, but in a Pool it will become a nightmare because you can end up having script fetch token too often and then some request ends up with out-dated token.
Anyway here is a rough example:
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
function my_middleware()
{
return function (callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
/**
* #var $promise \GuzzleHttp\Promise\Promise
*/
$promise = $handler($request, $options);
return $promise->then(
function (ResponseInterface $response) use ($request, $options) {
if ($response->getStatusCode() === 404) {
var_dump($response->getStatusCode());
var_dump(strlen($response->getBody()));
// Pretend we are getting new token key here
$client = new Client();
$key = $client->get('https://www.iana.org/domains/reserved');
// Then we modify the failed request. For your case you use ->withHeader() to change the
// Authorization header with your token.
$uri = $request->getUri();
$uri = $uri->withHost('google.com')->withPath('/');
// New instance of Request
$request = $request->withUri($uri);
// Send the request again with our new header/URL/whatever
return $client->sendAsync($request, $options);
}
return $response;
}
);
};
};
}
$handlerStack = HandlerStack::create();
$handlerStack->push(my_middleware());
$client = new Client([
'base_uri' => 'https://example.org',
'http_errors' => false,
'handler' => $handlerStack
]);
$options = [];
$response = $client->request('GET', '/test', $options);
var_dump($response->getStatusCode());
var_dump(strlen($response->getBody()));
echo $response->getBody();

\React\Promise\all($promises) doesn't work as expected in ReactPHP

Please note that I've also created an issue on the related repo.
In the documentation, it says that this function will return a promise which will resolved after all the promises in the array have resolved.
Here is my implementation;
private function downloadListingImages($contents)
{
$response = [];
$name = 1;
foreach ($contents['images'] as $image) {
$response[] = $this->downloadImage($image, $name);
$name++;
}
return $response;
}
private function downloadImage($link, $name)
{
$guzzle = new Client([
'handler' => HandlerStack::create(new HttpClientAdapter($this->loop)),
]);
$promise = $guzzle->getAsync($link, [
'save_to' => 'assets/' . $name . '.jpg',
]);
return $promise;
}
$promises = $this->downloadListingImages($contents);
Now, everything is fine till this point. But want I want to do is get $contents from a request to my server. So I have a server implementation;
$server = new React\Http\Server(function (Psr\Http\Message\ServerRequestInterface $request) use ($promises) {
\React\Promise\all($promises)->always(function($val) {
file_put_contents('meh.txt', "meh");
});
return new React\Http\Response(
200,
array('Content-Type' => 'text/plain'),
"Hello World!\n"
);
});
What I expect here that $server returns an immediate response (which it does) and after a while see the meh.txt in my repo. However, it never falls to always callback. And even when I don't chain any function on all method, it just resolves itself. Shouldn't it wait until then or something similar to be called to be resolved? How can run my guzzle promises async and get informed when the work is finished?
As long as the objects that passed to \React\Promise\all implement the method then, it works very well. E.g. \React\Promise\all works very fine with with \React\Promise\Promise() and also with \GuzzleHttp\Promise\Promise.
So far I'm not able to reproduce this issue.
As you can see in the documentation the always-method doesn't accept any parameter. Consider to use then instead of always if you in need of a parameter.
Also consider that file_put_contents MAY blocking the event-loop.
I hope this helps.

How to send multiple requests at once ReactPHP?

I am using guzzle to send some requests:
$response = $this->client->request(new SomeObject());
using the class below
...
public function request(Request $request)
{
return $this->requestAsync($request)->wait();
}
// Here I'm using Guzzle Async, but I can remove that is needed
public function requestAsync(Request $request)
{
$promise = $this->http->requestAsync($request->getMethod(), $request->getUri());
$promise = $promise->then(
function (ResponseInterface $response) use ($request) {
return $response;
}
);
return $promise;
}
...
I would like to use ReactPHP to send multiple requests at once in a foreach loop:
$requests = [];
foreach ($data as $value) {
$requests[] = $this->client->request(new SomeObject());
}
// pass $requests to ReactPHP here and wait on the response
Any ideas?
First of all, you don't need ReactPHP to use parallel HTTP requests with Guzzle. Guzzle itself provides this feature (if you use cURL handler, which is the default).
For example:
$promises = [];
foreach ($data as $value) {
$promises[] = $guzzleClient->getAsync(/* some URL */);
}
// Combine all promises
$combinedPromise = \GuzzleHttp\Promise\all($promises)
// And wait for them to finish (all requests are executed in parallel)
$responses = $combinedPromise->wait();
If you still want to use Guzzle with ReactPHP event loop, then, unfortunately, there are no straightforward solution. You cat take a look at https://github.com/productsupcom/guzzle-react-bridge (I'm the developer, so feel free to ask questions).

Mock response and use history middleware at the same time in Guzzle

Is there any way to mock response and request in Guzzle?
I have a class which sends some request and I want to test.
In Guzzle doc I found a way how can I mock response and request separately. But how can I combine them?
Because, If use history stack, guzzle trying to send a real request.
And visa verse, when I mock response handler can't test request.
class MyClass {
public function __construct($guzzleClient) {
$this->client = $guzzleClient;
}
public function registerUser($name, $lang)
{
$body = ['name' => $name, 'lang' = $lang, 'state' => 'online'];
$response = $this->sendRequest('PUT', '/users', ['body' => $body];
return $response->getStatusCode() == 201;
}
protected function sendRequest($method, $resource, array $options = [])
{
try {
$response = $this->client->request($method, $resource, $options);
} catch (BadResponseException $e) {
$response = $e->getResponse();
}
$this->response = $response;
return $response;
}
}
Test:
class MyClassTest {
//....
public function testRegisterUser()
{
$guzzleMock = new \GuzzleHttp\Handler\MockHandler([
new \GuzzleHttp\Psr7\Response(201, [], 'user created response'),
]);
$guzzleClient = new \GuzzleHttp\Client(['handler' => $guzzleMock]);
$myClass = new MyClass($guzzleClient);
/**
* But how can I check that request contains all fields that I put in the body? Or if I add some extra header?
*/
$this->assertTrue($myClass->registerUser('John Doe', 'en'));
}
//...
}
#Alex Blex was very close.
Solution:
$container = [];
$history = \GuzzleHttp\Middleware::history($container);
$guzzleMock = new \GuzzleHttp\Handler\MockHandler([
new \GuzzleHttp\Psr7\Response(201, [], 'user created response'),
]);
$stack = \GuzzleHttp\HandlerStack::create($guzzleMock);
$stack->push($history);
$guzzleClient = new \GuzzleHttp\Client(['handler' => $stack]);
First of all, you don't mock requests. The requests are the real ones you are going to use in production. The mock handler is actually a stack, so you can push multiple handlers there:
$container = [];
$history = \GuzzleHttp\Middleware::history($container);
$stack = \GuzzleHttp\Handler\MockHandler::createWithMiddleware([
new \GuzzleHttp\Psr7\Response(201, [], 'user created response'),
]);
$stack->push($history);
$guzzleClient = new \GuzzleHttp\Client(['handler' => $stack]);
After you run your tests, $container will have all transactions for you to assert. In your particular test - a single transaction. You are interested in $container[0]['request'], since $container[0]['response'] will contain your canned response, so there is nothing to assert really.

Slim 3 post method returns empty

When I submit a from action to this url, and also test this api via Postman. It is not printing POST data.
But get method is working.
$app = new \Slim\App;
$dotenv = new Dotenv\Dotenv(__DIR__);
$dotenv->load();
$app->add(new \Slim\Middleware\JwtAuthentication([
//"secure" => false,
//"relaxed" => ["localhost", "api.f2f.dev"],
"header" => "X-Token",
"path" => ["/v2"],
"passthrough" => ["/v1/api/token/", "/test", "/v1"],
"secret" => getenv("TOKEN_SECRET")
]));
$app->post("/v1/app/register", function ($request, $response, $arguments) {
return $allPostPutVars = $request->getParsedBody();
});
I couldn't find the issue i this. But unparsed data is able to print.
Any help is welcome. Any post method on Slim 3 is also welcomed.
Thank you.
Your callback should return response object that implements Psr\Http\Message\ResponseInterface. It doesn't. So, as an example:
$app->post("/v1/app/register", function ($request, $response, $arguments) {
$params = $request->getParams();
return $response->getBody()->write('You have posted '.count($params).' parameters.');
});
Sometimes you need a quick and dirty check. Then you can do the following:
$app->post("/v1/app/register", function ($request, $response, $arguments) {
$params = $request->getParams();
print_r($params);
die();
});

Categories