Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure HTTP server options via parameters #23

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -24,14 +31,16 @@ 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
- sudo apt-get install beanstalkd

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/

Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really happy with this change.... But with the higher versions I ran into problems with phpstan and ecs which weren't straight forward to fix (PhpStan complaining about amphp code, ecs refusing to run at all)

"symplify/easy-coding-standard-prefixed": "^8.1 <8.3"
},
"scripts": {
"phpcs": "phpcs",
Expand Down
2 changes: 2 additions & 0 deletions esb.yml.sample
Original file line number Diff line number Diff line change
@@ -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 <fromemail@address.com>" # From name/address for significant events mail notifications
console_port: 8080 # Web console port
Expand Down
1 change: 1 addition & 0 deletions services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions services_test.yml
Original file line number Diff line number Diff line change
@@ -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 <fromemail@address.com>"
console_port: '%env(int:ESB_CONSOLE_PORT)%'
Expand Down
2 changes: 1 addition & 1 deletion src/Exception/ElasticSearch/JobNotFoundException.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
69 changes: 66 additions & 3 deletions src/Service/HttpProducersServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
*/
Expand All @@ -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;
}

Expand All @@ -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();
Expand Down Expand Up @@ -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;
}
}
49 changes: 46 additions & 3 deletions tests/Integration/HttpRequestProducerAndWorkerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(
[
Expand All @@ -44,14 +46,17 @@ public function setUp()
'producer' => ['service' => DummyHttpRequestProducer::class],
'worker' => ['service' => DummyFilesystemWorker::class],
]
]
],
'parameters' => $additionalParameters,
]
);
$this->httpPort = self::$kernel->getContainer()->getParameter('http_server_port');
}

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']]);
Expand Down Expand Up @@ -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']]);
Expand All @@ -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) {
Expand All @@ -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);
}
}
}