diff --git a/.travis.yml b/.travis.yml index 680e571..c064a3c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,20 @@ sudo: required language: php -php: - - 7.2 - - 7.3 - - 7.4 + +jobs: + include: + # Combination PHP <7.4 and maglnet/composer-require-checker doesn't work with Composer 2 + - php: 7.2 + env: COMPOSER_VERSION=1.10.16 + - php: 7.3 + env: COMPOSER_VERSION=1.10.16 + - php: 7.4 + env: COMPOSER_VERSION=--stable env: - - ESB_CONSOLE_PORT=8080 ESB_HTTP_SERVER_PORT=34981 ESB_BEANSTALKD_URL=tcp://127.0.0.1:11300 ES_BASE_URI=http://127.0.0.1:9200 + global: + - ESB_CONSOLE_PORT=8080 ESB_HTTP_SERVER_PORT=34981 ESB_BEANSTALKD_URL=tcp://127.0.0.1:11300 ES_BASE_URI=http://127.0.0.1:9200 cache: directories: @@ -24,6 +31,8 @@ before_install: - echo -e '-Ddiscovery.type=single-node\n-XX:+DisableExplicitGC\n-Djdk.io.permissionsUseCanonicalPath=true\n-Dlog4j.skipJansi=true\n-server\n' | sudo tee -a /etc/elasticsearch/jvm.options - sudo chown -R elasticsearch:elasticsearch /etc/default/elasticsearch - sudo systemctl start elasticsearch + - composer --verbose self-update $COMPOSER_VERSION + - composer --version install: - sudo apt-get update @@ -31,7 +40,7 @@ install: before_script: - composer install - - composer global require maglnet/composer-require-checker && $HOME/.composer/vendor/bin/composer-require-checker --config-file=composer-require-checker.json; + - composer global require maglnet/composer-require-checker && $(composer config home)/vendor/bin/composer-require-checker --config-file=composer-require-checker.json; - vendor/bin/ecs check - vendor/bin/phpstan analyse --no-progress -l max -c phpstan.neon src/ diff --git a/composer.json b/composer.json index f4721fb..8ab1a28 100644 --- a/composer.json +++ b/composer.json @@ -56,8 +56,8 @@ "pda/pheanstalk": "^3.1", "mikey179/vfsstream": "^1.6", "amphp/artax": "^3.0", - "phpstan/phpstan": "^0.12", - "symplify/easy-coding-standard-prefixed": "^8.1" + "phpstan/phpstan": "^0.12 <=0.12.66", + "symplify/easy-coding-standard-prefixed": "^8.1 <8.3" }, "scripts": { "phpcs": "phpcs", diff --git a/esb.yml.sample b/esb.yml.sample index 2833b2d..d8a9813 100644 --- a/esb.yml.sample +++ b/esb.yml.sample @@ -1,6 +1,8 @@ parameters: beanstalkd: tcp://127.0.0.1:11300 # Beanstalkd connection URI http_server_port: 34981 # HTTP Server Port + http_server_options: # The HTTP server options, see \Amp\Http\Server\Options for + bodySizeLimit: 131072 # all available options and default values logger_mail_to: toemail@address.com # Email address where to send significant events mail notifications logger_mail_from: "From Name " # From name/address for significant events mail notifications console_port: 8080 # Web console port diff --git a/services.yml b/services.yml index 3ecd9df..b739ce2 100644 --- a/services.yml +++ b/services.yml @@ -15,6 +15,7 @@ services: Webgriffe\Esb\Service\HttpProducersServer: arguments: $port: '%http_server_port%' + $options: '%http_server_options%' Monolog\Handler\StreamHandler: class: \Monolog\Handler\StreamHandler diff --git a/services_test.yml b/services_test.yml index 5df9c8f..3ea9fcd 100644 --- a/services_test.yml +++ b/services_test.yml @@ -1,6 +1,7 @@ parameters: beanstalkd: '%env(string:ESB_BEANSTALKD_URL)%' http_server_port: '%env(int:ESB_HTTP_SERVER_PORT)%' + http_server_options: ~ logger_mail_to: "toemail@address.com" logger_mail_from: "From Name " console_port: '%env(int:ESB_CONSOLE_PORT)%' diff --git a/src/Exception/ElasticSearch/JobNotFoundException.php b/src/Exception/ElasticSearch/JobNotFoundException.php index f1f16ff..7566146 100644 --- a/src/Exception/ElasticSearch/JobNotFoundException.php +++ b/src/Exception/ElasticSearch/JobNotFoundException.php @@ -8,7 +8,7 @@ class JobNotFoundException extends \RuntimeException { - public function __construct(string $jobUuid, $code = 0, Throwable $previous = null) + public function __construct(string $jobUuid, int $code = 0, Throwable $previous = null) { parent::__construct(sprintf('Job with UUID "%s" has not been found.', $jobUuid), $code, $previous); } diff --git a/src/Service/HttpProducersServer.php b/src/Service/HttpProducersServer.php index 07ba4a3..0ca413e 100644 --- a/src/Service/HttpProducersServer.php +++ b/src/Service/HttpProducersServer.php @@ -5,6 +5,7 @@ namespace Webgriffe\Esb\Service; use Amp\CallableMaker; +use Amp\Http\Server\Options; use Amp\Http\Server\Request; use Amp\Http\Server\RequestHandler\CallableRequestHandler; use Amp\Http\Server\Response; @@ -25,10 +26,33 @@ class HttpProducersServer { use CallableMaker; + /** + * All available options with setter methods + */ + private const AVAILABLE_OPTIONS = [ + 'debug' => ['true' => 'withDebugMode', 'false' => 'withoutDebugMode'], + 'connectionLimit' => ['setter' => 'withConnectionLimit'], + 'connectionsPerIpLimit' => ['setter' => 'withConnectionsPerIpLimit'], + 'connectionTimeout' => ['setter' => 'withConnectionTimeout'], + 'concurrentStreamLimit' => ['setter' => 'withConcurrentStreamLimit'], + 'framesPerSecondLimit' => ['setter' => 'withFramesPerSecondLimit'], + 'minimumAverageFrameSize' => ['setter' => 'withMinimumAverageFrameSize'], + 'allowedMethods' => ['setter' => 'withAllowedMethods'], + 'bodySizeLimit' => ['setter' => 'withBodySizeLimit'], + 'headerSizeLimit' => ['setter' => 'withHeaderSizeLimit'], + 'chunkSize' => ['setter' => 'withChunkSize'], + 'compression' => ['true' => 'withCompression', 'false' => 'withoutCompression'], + 'allowHttp2Upgrade' => ['true' => 'withHttp2Upgrade', 'false' => 'withoutHttp2Upgrade'], + ]; + /** * @var int */ private $port; + /** + * @var mixed[]|null + */ + private $options; /** * @var LoggerInterface */ @@ -42,9 +66,15 @@ class HttpProducersServer */ private $httpServer; - public function __construct(int $port, LoggerInterface $logger) + /** + * @param int $port + * @param mixed[]|null $options + * @param LoggerInterface $logger + */ + public function __construct(int $port, ?array $options, LoggerInterface $logger) { $this->port = $port; + $this->options = $options; $this->logger = $logger; } @@ -59,10 +89,11 @@ public function start(): Promise Socket\listen("[::]:{$this->port}"), ]; - $this->httpServer = new \Amp\Http\Server\Server( + $this->httpServer = new Server( $sockets, new CallableRequestHandler($this->callableFromInstanceMethod('requestHandler')), - new NullLogger() + new NullLogger(), + $this->getServerOptions() ); yield $this->httpServer->start(); @@ -129,4 +160,36 @@ private function matchProducerInstance(Request $request) } return false; } + + /** + * Parse the $this->options array into an Options instance + * @return Options + */ + private function getServerOptions(): Options + { + $options = new Options(); + if ($this->options === null) { + return $options; + } + + foreach (static::AVAILABLE_OPTIONS as $optionName => $optionData) { + $optionValue = $this->options[$optionName] ?? null; + if ($optionValue === null) { + continue; + } + + if (isset($optionData['setter'])) { + // $options = $options->withChunkSize($optionValue); + $options = $options->{$optionData['setter']}($optionValue); + } elseif ($optionValue) { + // $options = $options->withDebugMode(); + $options = $options->{$optionData['true']}(); + } else { + // $options = $options->withoutDebugMode(); + $options = $options->{$optionData['false']}(); + } + } + + return $options; + } } diff --git a/tests/Integration/HttpRequestProducerAndWorkerTest.php b/tests/Integration/HttpRequestProducerAndWorkerTest.php index a06544d..0b6758f 100644 --- a/tests/Integration/HttpRequestProducerAndWorkerTest.php +++ b/tests/Integration/HttpRequestProducerAndWorkerTest.php @@ -5,15 +5,18 @@ use Amp\Artax\DefaultClient; use Amp\Artax\Request; use Amp\Artax\Response; +use Amp\Http\Server\Options; use Amp\Loop; use Amp\Promise; use Amp\Socket\ClientSocket; use Amp\Socket\ConnectException; use Monolog\Logger; use org\bovigo\vfs\vfsStream; +use ReflectionObject; use Webgriffe\Esb\DummyFilesystemWorker; use Webgriffe\Esb\DummyHttpRequestProducer; use Webgriffe\Esb\KernelTestCase; +use Webgriffe\Esb\Service\HttpProducersServer; use Webgriffe\Esb\TestUtils; use function Amp\call; use function Amp\File\exists; @@ -28,9 +31,8 @@ class HttpRequestProducerAndWorkerTest extends KernelTestCase private const FLOW_CODE = 'http_producer_flow'; - public function setUp() + private function setUpKernel(array $additionalParameters = []) { - parent::setUp(); $this->workerFile = vfsStream::url('root/worker.data'); self::createKernel( [ @@ -44,7 +46,8 @@ public function setUp() 'producer' => ['service' => DummyHttpRequestProducer::class], 'worker' => ['service' => DummyFilesystemWorker::class], ] - ] + ], + 'parameters' => $additionalParameters, ] ); $this->httpPort = self::$kernel->getContainer()->getParameter('http_server_port'); @@ -52,6 +55,8 @@ public function setUp() public function testHttpRequestProducerAndWorker() { + $this->setUpKernel(); + Loop::delay(100, function () { yield $this->waitForConnectionAvailable("tcp://127.0.0.1:{$this->httpPort}"); $payload = json_encode(['jobs' => ['job1', 'job2', 'job3']]); @@ -89,6 +94,8 @@ public function testHttpRequestProducerAndWorker() public function testHttpRequestProducerWithWrongUriShouldReturn404() { + $this->setUpKernel(); + Loop::delay(100, function () { yield $this->waitForConnectionAvailable("tcp://127.0.0.1:{$this->httpPort}"); $payload = json_encode(['jobs' => ['job1', 'job2', 'job3']]); @@ -108,6 +115,25 @@ public function testHttpRequestProducerWithWrongUriShouldReturn404() $this->assertReadyJobsCountInTube(0, self::FLOW_CODE); } + public function testHttpKernelSettings() + { + $this->setUpKernel(['http_server_options' => ['bodySizeLimit' => 42]]); + + Loop::delay(100, function () { + yield $this->waitForConnectionAvailable("tcp://127.0.0.1:{$this->httpPort}"); + + $httpProducersServer = self::$kernel->getContainer()->get(HttpProducersServer::class); + $httpServer = $this->getObjectProperty($httpProducersServer, 'httpServer'); + /** @var Options $serverOptions */ + $serverOptions = $this->getObjectProperty($httpServer, 'options'); + $this->assertSame(42, $serverOptions->getBodySizeLimit()); + + Loop::stop(); + }); + + self::$kernel->boot(); + } + private function waitForConnectionAvailable(string $uri): Promise { return call(function () use ($uri) { @@ -122,4 +148,21 @@ private function waitForConnectionAvailable(string $uri): Promise $connection->close(); }); } + + /** + * @param object $object + * @param string $propertyName + * @return mixed + * @throws \ReflectionException + */ + private function getObjectProperty(object $object, string $propertyName) + { + $reflectionProperty = (new ReflectionObject($object))->getProperty($propertyName); + $reflectionProperty->setAccessible(true); + try { + return $reflectionProperty->getValue($object); + } finally { + $reflectionProperty->setAccessible(false); + } + } }