init
This commit is contained in:
21
vendor/binsoul/net-mqtt-client-react/LICENSE.md
vendored
Executable file
21
vendor/binsoul/net-mqtt-client-react/LICENSE.md
vendored
Executable file
@@ -0,0 +1,21 @@
|
||||
# The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Sebastian Mößler <code@binsoul.de>
|
||||
|
||||
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
> of this software and associated documentation files (the "Software"), to deal
|
||||
> in the Software without restriction, including without limitation the rights
|
||||
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
> copies of the Software, and to permit persons to whom the Software is
|
||||
> furnished to do so, subject to the following conditions:
|
||||
>
|
||||
> The above copyright notice and this permission notice shall be included in
|
||||
> all copies or substantial portions of the Software.
|
||||
>
|
||||
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
> THE SOFTWARE.
|
||||
145
vendor/binsoul/net-mqtt-client-react/README.md
vendored
Executable file
145
vendor/binsoul/net-mqtt-client-react/README.md
vendored
Executable file
@@ -0,0 +1,145 @@
|
||||
# net-mqtt-client-react
|
||||
|
||||
[![Latest Version on Packagist][ico-version]][link-packagist]
|
||||
[![Software License][ico-license]](LICENSE.md)
|
||||
[![Total Downloads][ico-downloads]][link-downloads]
|
||||
|
||||
This package provides an asynchronous MQTT client built on the [React socket](https://github.com/reactphp/socket) library. All client methods return a promise which is fulfilled if the operation succeeded or rejected if the operation failed. Incoming messages of subscribed topics are delivered via the "message" event.
|
||||
|
||||
## Install
|
||||
|
||||
Via composer:
|
||||
|
||||
``` bash
|
||||
$ composer require binsoul/net-mqtt-client-react
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Connect to a public broker and run forever.
|
||||
|
||||
``` php
|
||||
<?php
|
||||
|
||||
use BinSoul\Net\Mqtt\Client\React\ReactMqttClient;
|
||||
use BinSoul\Net\Mqtt\Connection;
|
||||
use BinSoul\Net\Mqtt\DefaultMessage;
|
||||
use BinSoul\Net\Mqtt\DefaultSubscription;
|
||||
use BinSoul\Net\Mqtt\Message;
|
||||
use BinSoul\Net\Mqtt\Subscription;
|
||||
use React\Socket\DnsConnector;
|
||||
use React\Socket\TcpConnector;
|
||||
|
||||
include 'vendor/autoload.php';
|
||||
|
||||
// Setup client
|
||||
$loop = \React\EventLoop\Factory::create();
|
||||
$dnsResolverFactory = new \React\Dns\Resolver\Factory();
|
||||
$connector = new DnsConnector(new TcpConnector($loop), $dnsResolverFactory->createCached('8.8.8.8', $loop));
|
||||
$client = new ReactMqttClient($connector, $loop);
|
||||
|
||||
// Bind to events
|
||||
$client->on('open', function () use ($client) {
|
||||
// Network connection established
|
||||
echo sprintf("Open: %s:%s\n", $client->getHost(), $client->getPort());
|
||||
});
|
||||
|
||||
$client->on('close', function () use ($client, $loop) {
|
||||
// Network connection closed
|
||||
echo sprintf("Close: %s:%s\n", $client->getHost(), $client->getPort());
|
||||
|
||||
$loop->stop();
|
||||
});
|
||||
|
||||
$client->on('connect', function (Connection $connection) {
|
||||
// Broker connected
|
||||
echo sprintf("Connect: client=%s\n", $connection->getClientID());
|
||||
});
|
||||
|
||||
$client->on('disconnect', function (Connection $connection) {
|
||||
// Broker disconnected
|
||||
echo sprintf("Disconnect: client=%s\n", $connection->getClientID());
|
||||
});
|
||||
|
||||
$client->on('message', function (Message $message) {
|
||||
// Incoming message
|
||||
echo 'Message';
|
||||
|
||||
if ($message->isDuplicate()) {
|
||||
echo ' (duplicate)';
|
||||
}
|
||||
|
||||
if ($message->isRetained()) {
|
||||
echo ' (retained)';
|
||||
}
|
||||
|
||||
echo ': '.$message->getTopic().' => '.mb_strimwidth($message->getPayload(), 0, 50, '...');
|
||||
echo "\n";
|
||||
});
|
||||
|
||||
$client->on('warning', function (\Exception $e) {
|
||||
echo sprintf("Warning: %s\n", $e->getMessage());
|
||||
});
|
||||
|
||||
$client->on('error', function (\Exception $e) use ($loop) {
|
||||
echo sprintf("Error: %s\n", $e->getMessage());
|
||||
|
||||
$loop->stop();
|
||||
});
|
||||
|
||||
// Connect to broker
|
||||
$client->connect('test.mosquitto.org')->then(
|
||||
function () use ($client) {
|
||||
// Subscribe to all topics
|
||||
$client->subscribe(new DefaultSubscription('#'))
|
||||
->then(function (Subscription $subscription) {
|
||||
echo sprintf("Subscribe: %s\n", $subscription->getFilter());
|
||||
})
|
||||
->otherwise(function (\Exception $e) {
|
||||
echo sprintf("Error: %s\n", $e->getMessage());
|
||||
});
|
||||
|
||||
// Publish humidity once
|
||||
$client->publish(new DefaultMessage('sensors/humidity', '55%'))
|
||||
->then(function (Message $message) {
|
||||
echo sprintf("Publish: %s => %s\n", $message->getTopic(), $message->getPayload());
|
||||
})
|
||||
->otherwise(function (\Exception $e) {
|
||||
echo sprintf("Error: %s\n", $e->getMessage());
|
||||
});
|
||||
|
||||
// Publish a random temperature every 10 seconds
|
||||
$generator = function () {
|
||||
return mt_rand(-20, 30);
|
||||
};
|
||||
|
||||
$client->publishPeriodically(10, new DefaultMessage('sensors/temperature'), $generator)
|
||||
->progress(function (Message $message) {
|
||||
echo sprintf("Publish: %s => %s\n", $message->getTopic(), $message->getPayload());
|
||||
})
|
||||
->otherwise(function (\Exception $e) {
|
||||
echo sprintf("Error: %s\n", $e->getMessage());
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
$loop->run();
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
``` bash
|
||||
$ composer test
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
|
||||
|
||||
[ico-version]: https://img.shields.io/packagist/v/binsoul/net-mqtt-client-react.svg?style=flat-square
|
||||
[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
|
||||
[ico-downloads]: https://img.shields.io/packagist/dt/binsoul/net-mqtt-client-react.svg?style=flat-square
|
||||
|
||||
[link-packagist]: https://packagist.org/packages/binsoul/net-mqtt-client-react
|
||||
[link-downloads]: https://packagist.org/packages/binsoul/net-mqtt-client-react
|
||||
[link-author]: https://github.com/binsoul
|
||||
51
vendor/binsoul/net-mqtt-client-react/composer.json
vendored
Executable file
51
vendor/binsoul/net-mqtt-client-react/composer.json
vendored
Executable file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "binsoul/net-mqtt-client-react",
|
||||
"description": "Asynchronous MQTT client built on React",
|
||||
"keywords": [
|
||||
"net",
|
||||
"mqtt",
|
||||
"client"
|
||||
],
|
||||
"homepage": "https://github.com/binsoul/net-mqtt-client-react",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Sebastian Mößler",
|
||||
"email": "code@binsoul.de",
|
||||
"homepage": "https://github.com/binsoul",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "~5.6|~7.0",
|
||||
"binsoul/net-mqtt": "~0.2",
|
||||
"react/promise": "~2.0",
|
||||
"react/socket": "~0.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0||~5.0",
|
||||
"friendsofphp/php-cs-fixer": "~1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"BinSoul\\Net\\Mqtt\\Client\\React\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"BinSoul\\Test\\Net\\Mqtt\\Client\\React\\": "tests"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit",
|
||||
"fix-style": [
|
||||
"php-cs-fixer fix src",
|
||||
"php-cs-fixer fix tests"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
112
vendor/binsoul/net-mqtt-client-react/src/ReactFlow.php
vendored
Executable file
112
vendor/binsoul/net-mqtt-client-react/src/ReactFlow.php
vendored
Executable file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace BinSoul\Net\Mqtt\Client\React;
|
||||
|
||||
use BinSoul\Net\Mqtt\Flow;
|
||||
use BinSoul\Net\Mqtt\Packet;
|
||||
use React\Promise\Deferred;
|
||||
|
||||
/**
|
||||
* Decorates flows with data required for the {@see ReactMqttClient} class.
|
||||
*/
|
||||
class ReactFlow implements Flow
|
||||
{
|
||||
/** @var Flow */
|
||||
private $decorated;
|
||||
/** @var Deferred */
|
||||
private $deferred;
|
||||
/** @var Packet */
|
||||
private $packet;
|
||||
/** @var bool */
|
||||
private $isSilent;
|
||||
|
||||
/**
|
||||
* Constructs an instance of this class.
|
||||
*
|
||||
* @param Flow $decorated
|
||||
* @param Deferred $deferred
|
||||
* @param Packet $packet
|
||||
* @param bool $isSilent
|
||||
*/
|
||||
public function __construct(Flow $decorated, Deferred $deferred, Packet $packet = null, $isSilent = false)
|
||||
{
|
||||
$this->decorated = $decorated;
|
||||
$this->deferred = $deferred;
|
||||
$this->packet = $packet;
|
||||
$this->isSilent = $isSilent;
|
||||
}
|
||||
|
||||
public function getCode()
|
||||
{
|
||||
return $this->decorated->getCode();
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
$this->packet = $this->decorated->start();
|
||||
|
||||
return $this->packet;
|
||||
}
|
||||
|
||||
public function accept(Packet $packet)
|
||||
{
|
||||
return $this->decorated->accept($packet);
|
||||
}
|
||||
|
||||
public function next(Packet $packet)
|
||||
{
|
||||
$this->packet = $this->decorated->next($packet);
|
||||
|
||||
return $this->packet;
|
||||
}
|
||||
|
||||
public function isFinished()
|
||||
{
|
||||
return $this->decorated->isFinished();
|
||||
}
|
||||
|
||||
public function isSuccess()
|
||||
{
|
||||
return $this->decorated->isSuccess();
|
||||
}
|
||||
|
||||
public function getResult()
|
||||
{
|
||||
return $this->decorated->getResult();
|
||||
}
|
||||
|
||||
public function getErrorMessage()
|
||||
{
|
||||
return $this->decorated->getErrorMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the associated deferred.
|
||||
*
|
||||
* @return Deferred
|
||||
*/
|
||||
public function getDeferred()
|
||||
{
|
||||
return $this->deferred;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current packet.
|
||||
*
|
||||
* @return Packet
|
||||
*/
|
||||
public function getPacket()
|
||||
{
|
||||
return $this->packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the flow should emit events.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSilent()
|
||||
{
|
||||
return $this->isSilent;
|
||||
}
|
||||
}
|
||||
701
vendor/binsoul/net-mqtt-client-react/src/ReactMqttClient.php
vendored
Executable file
701
vendor/binsoul/net-mqtt-client-react/src/ReactMqttClient.php
vendored
Executable file
@@ -0,0 +1,701 @@
|
||||
<?php
|
||||
|
||||
namespace BinSoul\Net\Mqtt\Client\React;
|
||||
|
||||
use BinSoul\Net\Mqtt\Connection;
|
||||
use BinSoul\Net\Mqtt\DefaultConnection;
|
||||
use BinSoul\Net\Mqtt\DefaultIdentifierGenerator;
|
||||
use BinSoul\Net\Mqtt\Flow;
|
||||
use BinSoul\Net\Mqtt\Flow\IncomingPublishFlow;
|
||||
use BinSoul\Net\Mqtt\Flow\OutgoingConnectFlow;
|
||||
use BinSoul\Net\Mqtt\Flow\OutgoingDisconnectFlow;
|
||||
use BinSoul\Net\Mqtt\Flow\OutgoingPingFlow;
|
||||
use BinSoul\Net\Mqtt\Flow\OutgoingPublishFlow;
|
||||
use BinSoul\Net\Mqtt\Flow\OutgoingSubscribeFlow;
|
||||
use BinSoul\Net\Mqtt\Flow\OutgoingUnsubscribeFlow;
|
||||
use BinSoul\Net\Mqtt\DefaultMessage;
|
||||
use BinSoul\Net\Mqtt\IdentifierGenerator;
|
||||
use BinSoul\Net\Mqtt\Message;
|
||||
use BinSoul\Net\Mqtt\Packet;
|
||||
use BinSoul\Net\Mqtt\Packet\PublishRequestPacket;
|
||||
use BinSoul\Net\Mqtt\StreamParser;
|
||||
use BinSoul\Net\Mqtt\Subscription;
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\Timer\TimerInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\ExtendedPromiseInterface;
|
||||
use React\Promise\RejectedPromise;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Stream\DuplexStreamInterface;
|
||||
|
||||
/**
|
||||
* Connects to a MQTT broker and subscribes to topics or publishes messages.
|
||||
*
|
||||
* The following events are emitted:
|
||||
* - open - The network connection to the server is established.
|
||||
* - close - The network connection to the server is closed.
|
||||
* - warning - An event of severity "warning" occurred.
|
||||
* - error - An event of severity "error" occurred.
|
||||
*
|
||||
* If a flow finishes it's result is also emitted, e.g.:
|
||||
* - connect - The client connected to the broker.
|
||||
* - disconnect - The client disconnected from the broker.
|
||||
* - subscribe - The client subscribed to a topic filter.
|
||||
* - unsubscribe - The client unsubscribed from topic filter.
|
||||
* - publish - A message was published.
|
||||
* - message - A message was received.
|
||||
*/
|
||||
class ReactMqttClient extends EventEmitter
|
||||
{
|
||||
/** @var ConnectorInterface */
|
||||
private $connector;
|
||||
/** @var LoopInterface */
|
||||
private $loop;
|
||||
/** @var DuplexStreamInterface */
|
||||
private $stream;
|
||||
/** @var StreamParser */
|
||||
private $parser;
|
||||
/** @var IdentifierGenerator */
|
||||
private $identifierGenerator;
|
||||
|
||||
/** @var string */
|
||||
private $host;
|
||||
/** @var int */
|
||||
private $port;
|
||||
/** @var Connection */
|
||||
private $connection;
|
||||
/** @var bool */
|
||||
private $isConnected = false;
|
||||
/** @var bool */
|
||||
private $isConnecting = false;
|
||||
/** @var bool */
|
||||
private $isDisconnecting = false;
|
||||
|
||||
/** @var TimerInterface[] */
|
||||
private $timer = [];
|
||||
|
||||
/** @var ReactFlow[] */
|
||||
private $receivingFlows = [];
|
||||
/** @var ReactFlow[] */
|
||||
private $sendingFlows = [];
|
||||
/** @var ReactFlow */
|
||||
private $writtenFlow;
|
||||
|
||||
/**
|
||||
* Constructs an instance of this class.
|
||||
*
|
||||
* @param ConnectorInterface $connector
|
||||
* @param LoopInterface $loop
|
||||
* @param IdentifierGenerator $identifierGenerator
|
||||
* @param StreamParser $parser
|
||||
*/
|
||||
public function __construct(
|
||||
ConnectorInterface $connector,
|
||||
LoopInterface $loop,
|
||||
IdentifierGenerator $identifierGenerator = null,
|
||||
StreamParser $parser = null
|
||||
) {
|
||||
$this->connector = $connector;
|
||||
$this->loop = $loop;
|
||||
|
||||
$this->parser = $parser;
|
||||
if ($this->parser === null) {
|
||||
$this->parser = new StreamParser();
|
||||
}
|
||||
|
||||
$this->parser->onError(function (\Exception $e) {
|
||||
$this->emitWarning($e);
|
||||
});
|
||||
|
||||
$this->identifierGenerator = $identifierGenerator;
|
||||
if ($this->identifierGenerator === null) {
|
||||
$this->identifierGenerator = new DefaultIdentifierGenerator();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the host.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHost()
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the port.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPort()
|
||||
{
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the client is connected.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isConnected()
|
||||
{
|
||||
return $this->isConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying stream or null if the client is not connected.
|
||||
*
|
||||
* @return DuplexStreamInterface|null
|
||||
*/
|
||||
public function getStream()
|
||||
{
|
||||
return $this->stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to a broker.
|
||||
*
|
||||
* @param string $host
|
||||
* @param int $port
|
||||
* @param Connection $connection
|
||||
* @param int $timeout
|
||||
*
|
||||
* @return ExtendedPromiseInterface
|
||||
*/
|
||||
public function connect($host, $port = 1883, Connection $connection = null, $timeout = 5)
|
||||
{
|
||||
if ($this->isConnected || $this->isConnecting) {
|
||||
return new RejectedPromise(new \LogicException('The client is already connected.'));
|
||||
}
|
||||
|
||||
$this->isConnecting = true;
|
||||
$this->isConnected = false;
|
||||
|
||||
$this->host = $host;
|
||||
$this->port = $port;
|
||||
|
||||
if ($connection === null) {
|
||||
$connection = new DefaultConnection();
|
||||
}
|
||||
|
||||
if ($connection->isCleanSession()) {
|
||||
$this->cleanPreviousSession();
|
||||
}
|
||||
|
||||
if ($connection->getClientID() === '') {
|
||||
$connection = $connection->withClientID($this->identifierGenerator->generateClientID());
|
||||
}
|
||||
|
||||
$deferred = new Deferred();
|
||||
|
||||
$this->establishConnection($this->host, $this->port, $timeout)
|
||||
->then(function (DuplexStreamInterface $stream) use ($connection, $deferred, $timeout) {
|
||||
$this->stream = $stream;
|
||||
|
||||
$this->emit('open', [$connection, $this]);
|
||||
|
||||
$this->registerClient($connection, $timeout)
|
||||
->then(function (Connection $connection) use ($deferred) {
|
||||
$this->isConnecting = false;
|
||||
$this->isConnected = true;
|
||||
$this->connection = $connection;
|
||||
|
||||
$this->emit('connect', [$connection, $this]);
|
||||
$deferred->resolve($this->connection);
|
||||
})
|
||||
->otherwise(function (\Exception $e) use ($deferred, $connection) {
|
||||
$this->isConnecting = false;
|
||||
|
||||
$this->emitError($e);
|
||||
$deferred->reject($e);
|
||||
|
||||
if ($this->stream !== null) {
|
||||
$this->stream->close();
|
||||
}
|
||||
|
||||
$this->emit('close', [$connection, $this]);
|
||||
});
|
||||
})
|
||||
->otherwise(function (\Exception $e) use ($deferred) {
|
||||
$this->isConnecting = false;
|
||||
|
||||
$this->emitError($e);
|
||||
$deferred->reject($e);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from a broker.
|
||||
*
|
||||
* @return ExtendedPromiseInterface
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
if (!$this->isConnected || $this->isDisconnecting) {
|
||||
return new RejectedPromise(new \LogicException('The client is not connected.'));
|
||||
}
|
||||
|
||||
$this->isDisconnecting = true;
|
||||
|
||||
$deferred = new Deferred();
|
||||
|
||||
$this->startFlow(new OutgoingDisconnectFlow($this->connection), true)
|
||||
->then(function (Connection $connection) use ($deferred) {
|
||||
$this->isDisconnecting = false;
|
||||
$this->isConnected = false;
|
||||
|
||||
$this->emit('disconnect', [$connection, $this]);
|
||||
$deferred->resolve($connection);
|
||||
|
||||
if ($this->stream !== null) {
|
||||
$this->stream->close();
|
||||
}
|
||||
})
|
||||
->otherwise(function () use ($deferred) {
|
||||
$this->isDisconnecting = false;
|
||||
$deferred->reject($this->connection);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to a topic filter.
|
||||
*
|
||||
* @param Subscription $subscription
|
||||
*
|
||||
* @return ExtendedPromiseInterface
|
||||
*/
|
||||
public function subscribe(Subscription $subscription)
|
||||
{
|
||||
if (!$this->isConnected) {
|
||||
return new RejectedPromise(new \LogicException('The client is not connected.'));
|
||||
}
|
||||
|
||||
return $this->startFlow(new OutgoingSubscribeFlow([$subscription], $this->identifierGenerator));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes from a topic filter.
|
||||
*
|
||||
* @param Subscription $subscription
|
||||
*
|
||||
* @return ExtendedPromiseInterface
|
||||
*/
|
||||
public function unsubscribe(Subscription $subscription)
|
||||
{
|
||||
if (!$this->isConnected) {
|
||||
return new RejectedPromise(new \LogicException('The client is not connected.'));
|
||||
}
|
||||
|
||||
return $this->startFlow(new OutgoingUnsubscribeFlow([$subscription], $this->identifierGenerator));
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes a message.
|
||||
*
|
||||
* @param Message $message
|
||||
*
|
||||
* @return ExtendedPromiseInterface
|
||||
*/
|
||||
public function publish(Message $message)
|
||||
{
|
||||
if (!$this->isConnected) {
|
||||
return new RejectedPromise(new \LogicException('The client is not connected.'));
|
||||
}
|
||||
|
||||
return $this->startFlow(new OutgoingPublishFlow($message, $this->identifierGenerator));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the given generator periodically and publishes the return value.
|
||||
*
|
||||
* @param int $interval
|
||||
* @param Message $message
|
||||
* @param callable $generator
|
||||
*
|
||||
* @return ExtendedPromiseInterface
|
||||
*/
|
||||
public function publishPeriodically($interval, Message $message, callable $generator)
|
||||
{
|
||||
if (!$this->isConnected) {
|
||||
return new RejectedPromise(new \LogicException('The client is not connected.'));
|
||||
}
|
||||
|
||||
$deferred = new Deferred();
|
||||
|
||||
$this->timer[] = $this->loop->addPeriodicTimer(
|
||||
$interval,
|
||||
function () use ($message, $generator, $deferred) {
|
||||
$this->publish($message->withPayload($generator($message->getTopic())))->then(
|
||||
function ($value) use ($deferred) {
|
||||
$deferred->notify($value);
|
||||
},
|
||||
function (\Exception $e) use ($deferred) {
|
||||
$deferred->reject($e);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits warnings.
|
||||
*
|
||||
* @param \Exception $e
|
||||
*/
|
||||
private function emitWarning(\Exception $e)
|
||||
{
|
||||
$this->emit('warning', [$e, $this]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits errors.
|
||||
*
|
||||
* @param \Exception $e
|
||||
*/
|
||||
private function emitError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', [$e, $this]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes a network connection to a server.
|
||||
*
|
||||
* @param string $host
|
||||
* @param int $port
|
||||
* @param int $timeout
|
||||
*
|
||||
* @return ExtendedPromiseInterface
|
||||
*/
|
||||
private function establishConnection($host, $port, $timeout)
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
|
||||
$timer = $this->loop->addTimer(
|
||||
$timeout,
|
||||
function () use ($deferred, $timeout) {
|
||||
$exception = new \RuntimeException(sprintf('Connection timed out after %d seconds.', $timeout));
|
||||
$deferred->reject($exception);
|
||||
}
|
||||
);
|
||||
|
||||
$this->connector->connect($host.':'.$port)
|
||||
->always(function () use ($timer) {
|
||||
$this->loop->cancelTimer($timer);
|
||||
})
|
||||
->then(function (DuplexStreamInterface $stream) use ($deferred) {
|
||||
$stream->on('data', function ($data) {
|
||||
$this->handleReceive($data);
|
||||
});
|
||||
|
||||
$stream->on('close', function () {
|
||||
$this->handleClose();
|
||||
});
|
||||
|
||||
$stream->on('error', function (\Exception $e) {
|
||||
$this->handleError($e);
|
||||
});
|
||||
|
||||
$deferred->resolve($stream);
|
||||
})
|
||||
->otherwise(function (\Exception $e) use ($deferred) {
|
||||
$deferred->reject($e);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new client with the broker.
|
||||
*
|
||||
* @param Connection $connection
|
||||
* @param int $timeout
|
||||
*
|
||||
* @return ExtendedPromiseInterface
|
||||
*/
|
||||
private function registerClient(Connection $connection, $timeout)
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
|
||||
$responseTimer = $this->loop->addTimer(
|
||||
$timeout,
|
||||
function () use ($deferred, $timeout) {
|
||||
$exception = new \RuntimeException(sprintf('No response after %d seconds.', $timeout));
|
||||
$deferred->reject($exception);
|
||||
}
|
||||
);
|
||||
|
||||
$this->startFlow(new OutgoingConnectFlow($connection, $this->identifierGenerator), true)
|
||||
->always(function () use ($responseTimer) {
|
||||
$this->loop->cancelTimer($responseTimer);
|
||||
})->then(function (Connection $connection) use ($deferred) {
|
||||
$this->timer[] = $this->loop->addPeriodicTimer(
|
||||
floor($connection->getKeepAlive() * 0.75),
|
||||
function () {
|
||||
$this->startFlow(new OutgoingPingFlow());
|
||||
}
|
||||
);
|
||||
|
||||
$deferred->resolve($connection);
|
||||
})->otherwise(function (\Exception $e) use ($deferred) {
|
||||
$deferred->reject($e);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming data.
|
||||
*
|
||||
* @param string $data
|
||||
*/
|
||||
private function handleReceive($data)
|
||||
{
|
||||
if (!$this->isConnected && !$this->isConnecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
$flowCount = count($this->receivingFlows);
|
||||
|
||||
$packets = $this->parser->push($data);
|
||||
foreach ($packets as $packet) {
|
||||
$this->handlePacket($packet);
|
||||
}
|
||||
|
||||
if ($flowCount > count($this->receivingFlows)) {
|
||||
$this->receivingFlows = array_values($this->receivingFlows);
|
||||
}
|
||||
|
||||
$this->handleSend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an incoming packet.
|
||||
*
|
||||
* @param Packet $packet
|
||||
*/
|
||||
private function handlePacket(Packet $packet)
|
||||
{
|
||||
switch ($packet->getPacketType()) {
|
||||
case Packet::TYPE_PUBLISH:
|
||||
/* @var PublishRequestPacket $packet */
|
||||
$message = new DefaultMessage(
|
||||
$packet->getTopic(),
|
||||
$packet->getPayload(),
|
||||
$packet->getQosLevel(),
|
||||
$packet->isRetained(),
|
||||
$packet->isDuplicate()
|
||||
);
|
||||
|
||||
$this->startFlow(new IncomingPublishFlow($message, $packet->getIdentifier()));
|
||||
break;
|
||||
case Packet::TYPE_CONNACK:
|
||||
case Packet::TYPE_PINGRESP:
|
||||
case Packet::TYPE_SUBACK:
|
||||
case Packet::TYPE_UNSUBACK:
|
||||
case Packet::TYPE_PUBREL:
|
||||
case Packet::TYPE_PUBACK:
|
||||
case Packet::TYPE_PUBREC:
|
||||
case Packet::TYPE_PUBCOMP:
|
||||
$flowFound = false;
|
||||
foreach ($this->receivingFlows as $index => $flow) {
|
||||
if ($flow->accept($packet)) {
|
||||
$flowFound = true;
|
||||
|
||||
unset($this->receivingFlows[$index]);
|
||||
$this->continueFlow($flow, $packet);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$flowFound) {
|
||||
$this->emitWarning(
|
||||
new \LogicException(sprintf('Received unexpected packet of type %d.', $packet->getPacketType()))
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$this->emitWarning(
|
||||
new \LogicException(sprintf('Cannot handle packet of type %d.', $packet->getPacketType()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles outgoing packets.
|
||||
*/
|
||||
private function handleSend()
|
||||
{
|
||||
$flow = null;
|
||||
if ($this->writtenFlow !== null) {
|
||||
$flow = $this->writtenFlow;
|
||||
$this->writtenFlow = null;
|
||||
}
|
||||
|
||||
if (count($this->sendingFlows) > 0) {
|
||||
$this->writtenFlow = array_shift($this->sendingFlows);
|
||||
$this->stream->write($this->writtenFlow->getPacket());
|
||||
}
|
||||
|
||||
if ($flow !== null) {
|
||||
if ($flow->isFinished()) {
|
||||
$this->loop->nextTick(function () use ($flow) {
|
||||
$this->finishFlow($flow);
|
||||
});
|
||||
} else {
|
||||
$this->receivingFlows[] = $flow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles closing of the stream.
|
||||
*/
|
||||
private function handleClose()
|
||||
{
|
||||
foreach ($this->timer as $timer) {
|
||||
$this->loop->cancelTimer($timer);
|
||||
}
|
||||
|
||||
$this->timer = [];
|
||||
|
||||
$connection = $this->connection;
|
||||
|
||||
$this->isConnecting = false;
|
||||
$this->isDisconnecting = false;
|
||||
$this->isConnected = false;
|
||||
$this->connection = null;
|
||||
$this->stream = null;
|
||||
|
||||
if ($connection !== null) {
|
||||
$this->emit('close', [$connection, $this]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles errors of the stream.
|
||||
*
|
||||
* @param \Exception $e
|
||||
*/
|
||||
private function handleError(\Exception $e)
|
||||
{
|
||||
$this->emitError($e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the given flow.
|
||||
*
|
||||
* @param Flow $flow
|
||||
* @param bool $isSilent
|
||||
*
|
||||
* @return ExtendedPromiseInterface
|
||||
*/
|
||||
private function startFlow(Flow $flow, $isSilent = false)
|
||||
{
|
||||
try {
|
||||
$packet = $flow->start();
|
||||
} catch (\Exception $e) {
|
||||
$this->emitError($e);
|
||||
|
||||
return new RejectedPromise($e);
|
||||
}
|
||||
|
||||
$deferred = new Deferred();
|
||||
$internalFlow = new ReactFlow($flow, $deferred, $packet, $isSilent);
|
||||
|
||||
if ($packet !== null) {
|
||||
if ($this->writtenFlow !== null) {
|
||||
$this->sendingFlows[] = $internalFlow;
|
||||
} else {
|
||||
$this->stream->write($packet);
|
||||
$this->writtenFlow = $internalFlow;
|
||||
$this->handleSend();
|
||||
}
|
||||
} else {
|
||||
$this->loop->nextTick(function () use ($internalFlow) {
|
||||
$this->finishFlow($internalFlow);
|
||||
});
|
||||
}
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Continues the given flow.
|
||||
*
|
||||
* @param ReactFlow $flow
|
||||
* @param Packet $packet
|
||||
*/
|
||||
private function continueFlow(ReactFlow $flow, Packet $packet)
|
||||
{
|
||||
try {
|
||||
$response = $flow->next($packet);
|
||||
} catch (\Exception $e) {
|
||||
$this->emitError($e);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($response !== null) {
|
||||
if ($this->writtenFlow !== null) {
|
||||
$this->sendingFlows[] = $flow;
|
||||
} else {
|
||||
$this->stream->write($response);
|
||||
$this->writtenFlow = $flow;
|
||||
$this->handleSend();
|
||||
}
|
||||
} elseif ($flow->isFinished()) {
|
||||
$this->loop->nextTick(function () use ($flow) {
|
||||
$this->finishFlow($flow);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes the given flow.
|
||||
*
|
||||
* @param ReactFlow $flow
|
||||
*/
|
||||
private function finishFlow(ReactFlow $flow)
|
||||
{
|
||||
if ($flow->isSuccess()) {
|
||||
if (!$flow->isSilent()) {
|
||||
$this->emit($flow->getCode(), [$flow->getResult(), $this]);
|
||||
}
|
||||
|
||||
$flow->getDeferred()->resolve($flow->getResult());
|
||||
} else {
|
||||
$result = new \RuntimeException($flow->getErrorMessage());
|
||||
$this->emitWarning($result);
|
||||
|
||||
$flow->getDeferred()->reject($result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans previous session by rejecting all pending flows.
|
||||
*/
|
||||
private function cleanPreviousSession()
|
||||
{
|
||||
$error = new \RuntimeException('Connection has been closed.');
|
||||
|
||||
foreach ($this->receivingFlows as $receivingFlow) {
|
||||
$receivingFlow->getDeferred()->reject($error);
|
||||
}
|
||||
|
||||
foreach ($this->sendingFlows as $sendingFlow) {
|
||||
$sendingFlow->getDeferred()->reject($error);
|
||||
}
|
||||
|
||||
$this->receivingFlows = [];
|
||||
$this->sendingFlows = [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user