Integrating Guzzle 6 Asynchronous Requests with ReactPHP

So, I built a slack bot nopolabs/yabot in php.

Yabot’s basic architecture looks like this:

Yabot opens a websocket connection to Slack’s RTM API and then runs a React event loop that receives messages and dispatches them to plugins.

The event loop is single threaded and plugins handle messages (or not) as they please. This means that if a plugin blocks on an operation the whole event loop comes to a standstill. Now I’m not the first person to have run into this, in fact the author of sagebind/slack-client, the Slack client library used by Yabot, wrote a blog post about it.

The gist of the matter is that Guzzle’s CurlMultiHandler has a tick() method that will check for completed requests and resolve their promises.

So I wrapped GuzzleHttp\Client in a class: Guzzle.php that creates a timer to call tick() periodically:

private function scheduleProcessing()
{
    if ($this->timer === null) {
        $self = & $this;
        $this->timer = $this->eventloop->addPeriodicTimer(0, \Closure::bind(function() use (&$self) {
            $this->tick();
            // Stop the timer when there are no more requests
            if (empty($this->handles) && queue()->isEmpty()) {
                $self->timer->cancel();
                $self->timer = null;
            }
        }, $this->handler, $this->handler));
    }
}

That’s pretty cool, but GuzzleHttp\Client accepts a handler stack as an option in the constructor, and CurlMultiHandler accepts a CurlFactory as an option in the constructor, so I wrote a library: nopolabs/react-aware-guzzle-client

This is how it is used:

public function newClient(
    LoopInterface $eventLoop,
    array $config = [],
    CurlFactory $curlFactory = null,
    LoggerInterface $logger = null) : Client
{
    $clientFactory = new ReactAwareGuzzleClientFactory();
    return $clientFactory->createGuzzleClient($eventLoop, $config, $curlFactory, $logger);
}

Comments are closed.