From 45edd675f9f1c4500dff111283963611e000c3af Mon Sep 17 00:00:00 2001 From: Jakub Kulhan Date: Sun, 6 Jan 2019 14:06:49 +0100 Subject: [PATCH] ng: new sync client interface --- .travis.yml | 3 +- LICENSE | 2 +- composer.json | 2 +- src/Bunny/NG/ClosableInterface.php | 23 +++ src/Bunny/NG/RabbitChannelInterface.php | 12 ++ src/Bunny/NG/RabbitClientInterface.php | 54 ++++++ .../NG/RabbitConnectionFactoryInterface.php | 19 +++ src/Bunny/NG/RabbitConnectionInterface.php | 19 +++ src/Bunny/NG/RabbitConnectionOptions.php | 158 ++++++++++++++++++ src/Bunny/NG/RabbitConsumerOptions.php | 49 ++++++ src/Bunny/NG/RabbitMessage.php | 10 ++ src/Bunny/NG/RabbitSubscriptionInterface.php | 31 ++++ src/Bunny/NG/Sasl/AnonymousMechanism.php | 20 +++ src/Bunny/NG/Sasl/ExternalMechanism.php | 20 +++ src/Bunny/NG/Sasl/PlainMechanism.php | 34 ++++ src/Bunny/NG/Sasl/SaslMechanismInterface.php | 28 ++++ src/Bunny/NG/TlsOptions.php | 85 ++++++++++ 17 files changed, 565 insertions(+), 4 deletions(-) create mode 100644 src/Bunny/NG/ClosableInterface.php create mode 100644 src/Bunny/NG/RabbitChannelInterface.php create mode 100644 src/Bunny/NG/RabbitClientInterface.php create mode 100644 src/Bunny/NG/RabbitConnectionFactoryInterface.php create mode 100644 src/Bunny/NG/RabbitConnectionInterface.php create mode 100644 src/Bunny/NG/RabbitConnectionOptions.php create mode 100644 src/Bunny/NG/RabbitConsumerOptions.php create mode 100644 src/Bunny/NG/RabbitMessage.php create mode 100644 src/Bunny/NG/RabbitSubscriptionInterface.php create mode 100644 src/Bunny/NG/Sasl/AnonymousMechanism.php create mode 100644 src/Bunny/NG/Sasl/ExternalMechanism.php create mode 100644 src/Bunny/NG/Sasl/PlainMechanism.php create mode 100644 src/Bunny/NG/Sasl/SaslMechanismInterface.php create mode 100644 src/Bunny/NG/TlsOptions.php diff --git a/.travis.yml b/.travis.yml index 38a9d9d..77a8dc2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,8 @@ sudo: required language: php php: - - 7.0 - - 7.1 - 7.2 + - 7.3 - nightly cache: diff --git a/LICENSE b/LICENSE index 957004d..aa74207 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015-2018 Jakub Kulhan +Copyright (c) 2015-2019 Jakub Kulhan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/composer.json b/composer.json index a2b910e..6736971 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ } ], "require": { - "php": "~7.0", + "php": "~7.2", "psr/log": "~1.0", "react/event-loop": "^1.0 || ^0.5 || ^0.4", "react/promise": "~2.2" diff --git a/src/Bunny/NG/ClosableInterface.php b/src/Bunny/NG/ClosableInterface.php new file mode 100644 index 0000000..28e1f46 --- /dev/null +++ b/src/Bunny/NG/ClosableInterface.php @@ -0,0 +1,23 @@ + + */ +interface ClosableInterface +{ + + /** + * Closes the resource. + * + * After the resource is closed behavior of any of its method is undefined. + * + * If the resource is not closed by calling this method, it will trigger an error in its destructor. + * + * @return void + */ + public function close(); + +} diff --git a/src/Bunny/NG/RabbitChannelInterface.php b/src/Bunny/NG/RabbitChannelInterface.php new file mode 100644 index 0000000..60fc4cc --- /dev/null +++ b/src/Bunny/NG/RabbitChannelInterface.php @@ -0,0 +1,12 @@ + + */ +interface RabbitChannelInterface extends ClosableInterface +{ + // TODO: channel methods +} diff --git a/src/Bunny/NG/RabbitClientInterface.php b/src/Bunny/NG/RabbitClientInterface.php new file mode 100644 index 0000000..7525a0f --- /dev/null +++ b/src/Bunny/NG/RabbitClientInterface.php @@ -0,0 +1,54 @@ + + */ +interface RabbitClientInterface +{ + + /** + * Publishes the message on the client-managed connection. + * + * If the client is not connected, a new connection will be opened. + * + * If the connection gets broken, a new one be opened. + * + * All publications use the same channel on the connection. + * + * @param RabbitMessage $message + * @return void + */ + public function publish(RabbitMessage $message); + + /** + * Consume from one or more queues on the client-managed connection. + * + * If the client is not connected, a new connection will be opened. + * + * If the connection gets broken and there are messages that has been handed over to the application, however, + * that weren't acknowledged, rejected, or nacked, an exception will be thrown. Otherwise a new connection will + * be opened. + * + * Every subscription gets a new channel. However, all consumers of the subscription share that channel. + * + * @return RabbitSubscriptionInterface + */ + public function subscribe(): RabbitSubscriptionInterface; + + /** + * Send heartbeat on the client-managed connection. + * + * If the client is not connected, a new connection will be opened. + * + * This method is supposed to be called either if the application tries to verify that the broker is available, + * or it received a message on a subscription and it knows that the heartbeat timeout would be triggered. Otherwise, + * e.g. when a subscription waits for new messages, heartbeats get sent automatically. + * + * @return void + */ + public function ping(); + +} diff --git a/src/Bunny/NG/RabbitConnectionFactoryInterface.php b/src/Bunny/NG/RabbitConnectionFactoryInterface.php new file mode 100644 index 0000000..f82cbc0 --- /dev/null +++ b/src/Bunny/NG/RabbitConnectionFactoryInterface.php @@ -0,0 +1,19 @@ + + */ +interface RabbitConnectionFactoryInterface +{ + + /** + * Opens a new connection with the factory connection options. + * + * Returned connection is not managed by the instance and must be explicitly closed by its `close()` method. + * + * @return RabbitConnectionInterface + */ + public function newConnection(): RabbitConnectionInterface; + +} diff --git a/src/Bunny/NG/RabbitConnectionInterface.php b/src/Bunny/NG/RabbitConnectionInterface.php new file mode 100644 index 0000000..9478c87 --- /dev/null +++ b/src/Bunny/NG/RabbitConnectionInterface.php @@ -0,0 +1,19 @@ + + */ +interface RabbitConnectionInterface extends ClosableInterface +{ + + /** + * Opens a new channel on the connection. + * + * @return RabbitChannelInterface + */ + public function newChannel(): RabbitChannelInterface; + +} diff --git a/src/Bunny/NG/RabbitConnectionOptions.php b/src/Bunny/NG/RabbitConnectionOptions.php new file mode 100644 index 0000000..d32791c --- /dev/null +++ b/src/Bunny/NG/RabbitConnectionOptions.php @@ -0,0 +1,158 @@ + + */ +final class RabbitConnectionOptions +{ + + /** + * Hostname or IP address of the server. + * + * @var string + */ + public $host; + + /** + * Server port. + * + * @var int + */ + public $port; + + /** + * Available SASL mechanisms. + * + * @var SaslMechanismInterface[] + */ + public $mechanisms; + + /** + * Server virtual host. + * + * @var string + */ + public $virtualHost; + + /** + * Heartbeat timeout defines max time the connection will be kept idle. Every `heartbeat` seconds the client will send + * heartbeat frame to the server. If client doesn't hear from the server in `heartbeat * 2` seconds, the connection + * is assumed to be dead. + * + * @var float + */ + public $heartbeat; + + /** + * Timeout to establish the connection (connection parameters tuning, SASL exchange, opening virtual host). + * + * @var float + */ + public $connectTimeout; + + /** + * Parameters for TLS-secured connections. + * + * @var TlsOptions|null + */ + public $tls; + + public static function new() + { + return new static(); + } + + public static function fromUrl(string $url) + { + throw new \LogicException("TODO"); + } + + /** + * @param string $host + * @return self + */ + public function setHost(string $host) + { + $this->host = $host; + return $this; + } + + /** + * @param int $port + * @return self + */ + public function setPort(int $port) + { + $this->port = $port; + return $this; + } + + /** + * @param SaslMechanismInterface[] $mechanisms + * @return self + */ + public function setMechanisms(array $mechanisms) + { + $this->mechanisms = $mechanisms; + return $this; + } + + /** + * @param SaslMechanismInterface $mechanism + * @return self + */ + public function addMechanism(SaslMechanismInterface $mechanism) + { + if ($this->mechanisms === null) { + $this->mechanisms = []; + } + + $this->mechanisms[] = $mechanism; + + return $this; + } + + /** + * @param string $virtualHost + * @return self + */ + public function setVirtualHost(string $virtualHost) + { + $this->virtualHost = $virtualHost; + return $this; + } + + /** + * @param float $heartbeat + * @return self + */ + public function setHeartbeat(float $heartbeat) + { + $this->heartbeat = $heartbeat; + return $this; + } + + /** + * @param float $connectTimeout + * @return self + */ + public function setConnectTimeout(float $connectTimeout) + { + $this->connectTimeout = $connectTimeout; + return $this; + } + + /** + * @param TlsOptions|null $tls + * @return self + */ + public function setTls(?TlsOptions $tls) + { + $this->tls = $tls; + return $this; + } + +} diff --git a/src/Bunny/NG/RabbitConsumerOptions.php b/src/Bunny/NG/RabbitConsumerOptions.php new file mode 100644 index 0000000..3f4972e --- /dev/null +++ b/src/Bunny/NG/RabbitConsumerOptions.php @@ -0,0 +1,49 @@ + + */ +final class RabbitConsumerOptions +{ + + /** + * Queue name. + * + * @var string + */ + public $queue; + + /** + * Consumer tag. + * + * @var string + */ + public $consumerTag; + + public static function new() + { + return new static(); + } + + /** + * @param string $queue + * @return self + */ + public function setQueue(string $queue) + { + $this->queue = $queue; + return $this; + } + + /** + * @param string $consumerTag + * @return self + */ + public function setConsumerTag(string $consumerTag) + { + $this->consumerTag = $consumerTag; + return $this; + } + +} diff --git a/src/Bunny/NG/RabbitMessage.php b/src/Bunny/NG/RabbitMessage.php new file mode 100644 index 0000000..4fc498f --- /dev/null +++ b/src/Bunny/NG/RabbitMessage.php @@ -0,0 +1,10 @@ + + */ +final class RabbitMessage +{ + // TODO +} diff --git a/src/Bunny/NG/RabbitSubscriptionInterface.php b/src/Bunny/NG/RabbitSubscriptionInterface.php new file mode 100644 index 0000000..925991d --- /dev/null +++ b/src/Bunny/NG/RabbitSubscriptionInterface.php @@ -0,0 +1,31 @@ + + */ +interface RabbitSubscriptionInterface extends \Iterator, ClosableInterface +{ + + /** + * @param int $prefetchCount + * @return self + */ + public function setPrefetchCount(int $prefetchCount); + + /** + * @param string $queueName + * @param string|null $consumerTag + * @return self + */ + public function add(string $queueName, ?string &$consumerTag = null); + + /** + * @param string $consumerTag + * @return self + */ + public function remove(string $consumerTag); + +} diff --git a/src/Bunny/NG/Sasl/AnonymousMechanism.php b/src/Bunny/NG/Sasl/AnonymousMechanism.php new file mode 100644 index 0000000..d30611d --- /dev/null +++ b/src/Bunny/NG/Sasl/AnonymousMechanism.php @@ -0,0 +1,20 @@ + + */ +final class AnonymousMechanism implements SaslMechanismInterface +{ + + public function mechanism(): string + { + return "ANONYMOUS"; + } + + public function respondTo(?string $challenge): string + { + return ""; + } + +} diff --git a/src/Bunny/NG/Sasl/ExternalMechanism.php b/src/Bunny/NG/Sasl/ExternalMechanism.php new file mode 100644 index 0000000..7c55406 --- /dev/null +++ b/src/Bunny/NG/Sasl/ExternalMechanism.php @@ -0,0 +1,20 @@ + + */ +final class ExternalMechanism implements SaslMechanismInterface +{ + + public function mechanism(): string + { + return "EXTERNAL"; + } + + public function respondTo(?string $challenge): string + { + return ""; + } + +} diff --git a/src/Bunny/NG/Sasl/PlainMechanism.php b/src/Bunny/NG/Sasl/PlainMechanism.php new file mode 100644 index 0000000..e87b407 --- /dev/null +++ b/src/Bunny/NG/Sasl/PlainMechanism.php @@ -0,0 +1,34 @@ + + */ +final class PlainMechanism implements SaslMechanismInterface +{ + + /** @var string */ + private $username; + + /** @var string */ + private $password; + + public function __construct(string $username, string $password) + { + $this->username = $username; + $this->password = $password; + } + + public function mechanism(): string + { + return "PLAIN"; + } + + public function respondTo(?string $challenge): string + { + return sprintf("\x00%s\x00%s", $this->username, $this->password); + } + +} diff --git a/src/Bunny/NG/Sasl/SaslMechanismInterface.php b/src/Bunny/NG/Sasl/SaslMechanismInterface.php new file mode 100644 index 0000000..53ae1da --- /dev/null +++ b/src/Bunny/NG/Sasl/SaslMechanismInterface.php @@ -0,0 +1,28 @@ + + */ +interface SaslMechanismInterface +{ + + /** + * Returns SASL mechanism name. + * + * @return string + */ + public function mechanism(): string; + + /** + * Respond to server SASL challenge. + * + * @param string|null $challenge For initial response challenge will be `null`. + * @return string + */ + public function respondTo(?string $challenge): string; + +} diff --git a/src/Bunny/NG/TlsOptions.php b/src/Bunny/NG/TlsOptions.php new file mode 100644 index 0000000..2b17713 --- /dev/null +++ b/src/Bunny/NG/TlsOptions.php @@ -0,0 +1,85 @@ + + */ +final class TlsOptions +{ + + /** + * Path to file with PEM-encoded certificate authority data. + * + * @var string + */ + public $certificateAuthorityPath; + + /** + * Path to file with PEM-encoded client certificate AND private key. + * + * @var string + */ + public $certificatePrivateKeyPath; + + /** + * Passphrase for client private key. + * + * @var string + */ + public $privateKeyPassword; + + /** + * If true, connection will accept any certificate presented by server and hostname it declares. + * + * This SHOULD NOT be used in production as connections are then susceptible to man-in-the-middle attacks. + * + * @var bool + */ + public $insecureSkipVerify = false; + + public static function new() + { + return new static(); + } + + /** + * @param string $certificateAuthorityPath + * @return self + */ + public function setCertificateAuthorityPath(string $certificateAuthorityPath) + { + $this->certificateAuthorityPath = $certificateAuthorityPath; + return $this; + } + + /** + * @param string $certificatePrivateKeyPath + * @return self + */ + public function setCertificatePrivateKeyPath(string $certificatePrivateKeyPath) + { + $this->certificatePrivateKeyPath = $certificatePrivateKeyPath; + return $this; + } + + /** + * @param string $privateKeyPassword + * @return self + */ + public function setPrivateKeyPassword(string $privateKeyPassword) + { + $this->privateKeyPassword = $privateKeyPassword; + return $this; + } + + /** + * @param bool $insecureSkipVerify + * @return self + */ + public function setInsecureSkipVerify(bool $insecureSkipVerify) + { + $this->insecureSkipVerify = $insecureSkipVerify; + return $this; + } + +}