Allow Redis to be disabled in favor of flatfile cache.
This commit is contained in:
parent
3e8f90151f
commit
edb1839cbc
|
@ -54,6 +54,10 @@ ENV LANG="en_US.UTF-8" \
|
||||||
MYSQL_USER="azuracast" \
|
MYSQL_USER="azuracast" \
|
||||||
MYSQL_PASSWORD="azur4c457" \
|
MYSQL_PASSWORD="azur4c457" \
|
||||||
MYSQL_DATABASE="azuracast" \
|
MYSQL_DATABASE="azuracast" \
|
||||||
|
ENABLE_REDIS="true" \
|
||||||
|
REDIS_HOST="redis" \
|
||||||
|
REDIS_PORT=6379 \
|
||||||
|
REDIS_DB=1 \
|
||||||
PREFER_RELEASE_BUILDS="false" \
|
PREFER_RELEASE_BUILDS="false" \
|
||||||
COMPOSER_PLUGIN_MODE="false" \
|
COMPOSER_PLUGIN_MODE="false" \
|
||||||
ADDITIONAL_MEDIA_SYNC_WORKER_COUNT=0 \
|
ADDITIONAL_MEDIA_SYNC_WORKER_COUNT=0 \
|
||||||
|
|
|
@ -85,6 +85,10 @@ MYSQL_MAX_CONNECTIONS=100
|
||||||
# Do not modify these fields if you are using the standard AzuraCast Redis host.
|
# Do not modify these fields if you are using the standard AzuraCast Redis host.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# Whether to use the Redis cache; if set to false, will disable Redis and use flatfile cache instead.
|
||||||
|
# Default: true
|
||||||
|
# ENABLE_REDIS=true
|
||||||
|
|
||||||
# Name of the Redis host.
|
# Name of the Redis host.
|
||||||
# Default: redis
|
# Default: redis
|
||||||
# REDIS_HOST=redis
|
# REDIS_HOST=redis
|
||||||
|
|
|
@ -116,10 +116,16 @@ class UptimeWait
|
||||||
|
|
||||||
protected function checkRedis(): bool
|
protected function checkRedis(): bool
|
||||||
{
|
{
|
||||||
|
$enableRedis = $this->envToBool($_ENV['ENABLE_REDIS'] ?? true);
|
||||||
$redisHost = $_ENV['REDIS_HOST'] ?? 'redis';
|
$redisHost = $_ENV['REDIS_HOST'] ?? 'redis';
|
||||||
$redisPort = (int)($_ENV['REDIS_PORT'] ?? 6379);
|
$redisPort = (int)($_ENV['REDIS_PORT'] ?? 6379);
|
||||||
$redisDb = (int)($_ENV['REDIS_DB'] ?? 1);
|
$redisDb = (int)($_ENV['REDIS_DB'] ?? 1);
|
||||||
|
|
||||||
|
if (!$enableRedis) {
|
||||||
|
$this->println('Redis disabled; skipping Redis check...');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$redis = new Redis();
|
$redis = new Redis();
|
||||||
$redis->connect($redisHost, $redisPort, 15);
|
$redis->connect($redisHost, $redisPort, 15);
|
||||||
|
@ -137,6 +143,17 @@ class UptimeWait
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function envToBool(string|bool $value): bool
|
||||||
|
{
|
||||||
|
if (is_bool($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str_starts_with(strtolower($value), 'y')
|
||||||
|
|| 'true' === strtolower($value)
|
||||||
|
|| '1' === $value;
|
||||||
|
}
|
||||||
|
|
||||||
protected function println(string $line): void
|
protected function println(string $line): void
|
||||||
{
|
{
|
||||||
echo $line . "\n";
|
echo $line . "\n";
|
||||||
|
|
|
@ -72,6 +72,7 @@
|
||||||
"symfony/messenger": "^5",
|
"symfony/messenger": "^5",
|
||||||
"symfony/process": "^5",
|
"symfony/process": "^5",
|
||||||
"symfony/property-access": "^5",
|
"symfony/property-access": "^5",
|
||||||
|
"symfony/rate-limiter": "^5.3",
|
||||||
"symfony/redis-messenger": "^5",
|
"symfony/redis-messenger": "^5",
|
||||||
"symfony/serializer": "^5",
|
"symfony/serializer": "^5",
|
||||||
"symfony/validator": "^5",
|
"symfony/validator": "^5",
|
||||||
|
|
153
composer.lock
generated
153
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "c1a93d3233ba7369caefc4d1a25d4118",
|
"content-hash": "ff6df4489010493b9a43e5f2f41a7124",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-sdk-php",
|
"name": "aws/aws-sdk-php",
|
||||||
|
@ -7335,6 +7335,75 @@
|
||||||
],
|
],
|
||||||
"time": "2021-05-26T17:43:10+00:00"
|
"time": "2021-05-26T17:43:10+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/options-resolver",
|
||||||
|
"version": "v5.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/options-resolver.git",
|
||||||
|
"reference": "162e886ca035869866d233a2bfef70cc28f9bbe5"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/162e886ca035869866d233a2bfef70cc28f9bbe5",
|
||||||
|
"reference": "162e886ca035869866d233a2bfef70cc28f9bbe5",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2.5",
|
||||||
|
"symfony/deprecation-contracts": "^2.1",
|
||||||
|
"symfony/polyfill-php73": "~1.0",
|
||||||
|
"symfony/polyfill-php80": "^1.15"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\OptionsResolver\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Provides an improved replacement for the array_replace PHP function",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"config",
|
||||||
|
"configuration",
|
||||||
|
"options"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/options-resolver/tree/v5.3.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-05-26T17:43:10+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-ctype",
|
"name": "symfony/polyfill-ctype",
|
||||||
"version": "v1.23.0",
|
"version": "v1.23.0",
|
||||||
|
@ -7734,6 +7803,76 @@
|
||||||
],
|
],
|
||||||
"time": "2021-05-31T12:40:48+00:00"
|
"time": "2021-05-31T12:40:48+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/rate-limiter",
|
||||||
|
"version": "v5.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/rate-limiter.git",
|
||||||
|
"reference": "e9226c91163495ff0b655cdae0fff682e869640b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/rate-limiter/zipball/e9226c91163495ff0b655cdae0fff682e869640b",
|
||||||
|
"reference": "e9226c91163495ff0b655cdae0fff682e869640b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2.5",
|
||||||
|
"symfony/lock": "^5.2",
|
||||||
|
"symfony/options-resolver": "^5.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"psr/cache": "^1.0|^2.0|^3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\RateLimiter\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Wouter de Jong",
|
||||||
|
"email": "wouter@wouterj.nl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Provides a Token Bucket implementation to rate limit input and output in your application",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"limiter",
|
||||||
|
"rate-limiter"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/rate-limiter/tree/v5.3.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-05-26T17:43:10+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/redis-messenger",
|
"name": "symfony/redis-messenger",
|
||||||
"version": "v5.3.0",
|
"version": "v5.3.0",
|
||||||
|
@ -10650,16 +10789,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan",
|
"name": "phpstan/phpstan",
|
||||||
"version": "0.12.89",
|
"version": "0.12.90",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan.git",
|
"url": "https://github.com/phpstan/phpstan.git",
|
||||||
"reference": "54c0f5a6c30511b77128d58b6369f718df250542"
|
"reference": "f0e4b56630fc3d4eb5be86606d07212ac212ede4"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/54c0f5a6c30511b77128d58b6369f718df250542",
|
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/f0e4b56630fc3d4eb5be86606d07212ac212ede4",
|
||||||
"reference": "54c0f5a6c30511b77128d58b6369f718df250542",
|
"reference": "f0e4b56630fc3d4eb5be86606d07212ac212ede4",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -10690,7 +10829,7 @@
|
||||||
"description": "PHPStan - PHP Static Analysis Tool",
|
"description": "PHPStan - PHP Static Analysis Tool",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpstan/issues",
|
"issues": "https://github.com/phpstan/phpstan/issues",
|
||||||
"source": "https://github.com/phpstan/phpstan/tree/0.12.89"
|
"source": "https://github.com/phpstan/phpstan/tree/0.12.90"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -10710,7 +10849,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2021-06-09T20:23:49+00:00"
|
"time": "2021-06-18T07:15:38+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan-doctrine",
|
"name": "phpstan/phpstan-doctrine",
|
||||||
|
|
|
@ -134,6 +134,10 @@ return [
|
||||||
|
|
||||||
// Redis cache
|
// Redis cache
|
||||||
Redis::class => function (Environment $environment) {
|
Redis::class => function (Environment $environment) {
|
||||||
|
if (!$environment->enableRedis()) {
|
||||||
|
throw new App\Exception\BootstrapException('Redis is disabled on this installation.');
|
||||||
|
}
|
||||||
|
|
||||||
$settings = $environment->getRedisSettings();
|
$settings = $environment->getRedisSettings();
|
||||||
|
|
||||||
$redis = new Redis();
|
$redis = new Redis();
|
||||||
|
@ -148,15 +152,20 @@ return [
|
||||||
Psr\Log\LoggerInterface $logger,
|
Psr\Log\LoggerInterface $logger,
|
||||||
ContainerInterface $di
|
ContainerInterface $di
|
||||||
) {
|
) {
|
||||||
|
/** @var Symfony\Contracts\Cache\CacheInterface $cacheInterface */
|
||||||
if ($environment->isTesting()) {
|
if ($environment->isTesting()) {
|
||||||
$arrayAdapter = new Symfony\Component\Cache\Adapter\ArrayAdapter();
|
$cacheInterface = new Symfony\Component\Cache\Adapter\ArrayAdapter();
|
||||||
$arrayAdapter->setLogger($logger);
|
} elseif (!$environment->enableRedis()) {
|
||||||
return $arrayAdapter;
|
$tempDir = $environment->getTempDirectory() . DIRECTORY_SEPARATOR . 'cache';
|
||||||
|
$cacheInterface = new Symfony\Component\Cache\Adapter\FilesystemAdapter(
|
||||||
|
directory: $tempDir
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$cacheInterface = new Symfony\Component\Cache\Adapter\RedisAdapter($di->get(Redis::class));
|
||||||
}
|
}
|
||||||
|
|
||||||
$redisAdapter = new Symfony\Component\Cache\Adapter\RedisAdapter($di->get(Redis::class));
|
$cacheInterface->setLogger($logger);
|
||||||
$redisAdapter->setLogger($logger);
|
return $cacheInterface;
|
||||||
return $redisAdapter;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Symfony\Component\Cache\Adapter\AdapterInterface::class => DI\get(
|
Symfony\Component\Cache\Adapter\AdapterInterface::class => DI\get(
|
||||||
|
@ -178,9 +187,23 @@ return [
|
||||||
$psr6Cache = new Symfony\Component\Cache\Adapter\ArrayAdapter();
|
$psr6Cache = new Symfony\Component\Cache\Adapter\ArrayAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
$doctrineCache = Doctrine\Common\Cache\Psr6\DoctrineProvider::wrap($psr6Cache);
|
$proxyCache = new Symfony\Component\Cache\Adapter\ProxyAdapter($psr6Cache, 'doctrine.');
|
||||||
$doctrineCache->setNamespace('doctrine.');
|
return Doctrine\Common\Cache\Psr6\DoctrineProvider::wrap($proxyCache);
|
||||||
return $doctrineCache;
|
},
|
||||||
|
|
||||||
|
// Symfony Lock adapter
|
||||||
|
Symfony\Component\Lock\PersistingStoreInterface::class => function (
|
||||||
|
ContainerInterface $di,
|
||||||
|
Environment $environment
|
||||||
|
) {
|
||||||
|
if ($environment->enableRedis()) {
|
||||||
|
$redis = $di->get(Redis::class);
|
||||||
|
$store = new Symfony\Component\Lock\Store\RedisStore($redis);
|
||||||
|
} else {
|
||||||
|
$store = new Symfony\Component\Lock\Store\FlockStore($environment->getTempDirectory());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $store;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Session save handler middleware
|
// Session save handler middleware
|
||||||
|
@ -269,9 +292,11 @@ return [
|
||||||
Psr\Cache\CacheItemPoolInterface $psr6Cache,
|
Psr\Cache\CacheItemPoolInterface $psr6Cache,
|
||||||
Environment $settings
|
Environment $settings
|
||||||
) {
|
) {
|
||||||
|
$proxyCache = new Symfony\Component\Cache\Adapter\ProxyAdapter($psr6Cache, 'annotations.');
|
||||||
|
|
||||||
return new Doctrine\Common\Annotations\PsrCachedReader(
|
return new Doctrine\Common\Annotations\PsrCachedReader(
|
||||||
new Doctrine\Common\Annotations\AnnotationReader,
|
new Doctrine\Common\Annotations\AnnotationReader,
|
||||||
$psr6Cache,
|
$proxyCache,
|
||||||
!$settings->isProduction()
|
!$settings->isProduction()
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -441,12 +466,7 @@ return [
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$supervisor = new Supervisor\Supervisor($client, $logger);
|
return new Supervisor\Supervisor($client, $logger);
|
||||||
if (!$supervisor->isConnected()) {
|
|
||||||
throw new \App\Exception(sprintf('Could not connect to supervisord.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $supervisor;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Image Manager
|
// Image Manager
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Console\Command;
|
namespace App\Console\Command;
|
||||||
|
|
||||||
use App\Entity\Repository\SettingsRepository;
|
use App\Entity\Repository\SettingsRepository;
|
||||||
|
|
|
@ -59,6 +59,7 @@ class Environment
|
||||||
public const DB_USER = 'MYSQL_USER';
|
public const DB_USER = 'MYSQL_USER';
|
||||||
public const DB_PASSWORD = 'MYSQL_PASSWORD';
|
public const DB_PASSWORD = 'MYSQL_PASSWORD';
|
||||||
|
|
||||||
|
public const ENABLE_REDIS = 'ENABLE_REDIS';
|
||||||
public const REDIS_HOST = 'REDIS_HOST';
|
public const REDIS_HOST = 'REDIS_HOST';
|
||||||
public const REDIS_PORT = 'REDIS_PORT';
|
public const REDIS_PORT = 'REDIS_PORT';
|
||||||
public const REDIS_DB = 'REDIS_DB';
|
public const REDIS_DB = 'REDIS_DB';
|
||||||
|
@ -73,6 +74,8 @@ class Environment
|
||||||
|
|
||||||
self::ASSET_URL => '/static',
|
self::ASSET_URL => '/static',
|
||||||
|
|
||||||
|
self::ENABLE_REDIS => true,
|
||||||
|
|
||||||
self::SUPPORTED_LOCALES => [
|
self::SUPPORTED_LOCALES => [
|
||||||
'en_US.UTF-8' => 'English (Default)',
|
'en_US.UTF-8' => 'English (Default)',
|
||||||
'cs_CZ.UTF-8' => 'čeština', // Czech
|
'cs_CZ.UTF-8' => 'čeština', // Czech
|
||||||
|
@ -99,6 +102,17 @@ class Environment
|
||||||
$this->data = array_merge($this->defaults, $elements);
|
$this->data = array_merge($this->defaults, $elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function envToBool(string|bool $value): bool
|
||||||
|
{
|
||||||
|
if (is_bool($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str_starts_with(strtolower($value), 'y')
|
||||||
|
|| 'true' === strtolower($value)
|
||||||
|
|| '1' === $value;
|
||||||
|
}
|
||||||
|
|
||||||
public function getAppEnvironment(): string
|
public function getAppEnvironment(): string
|
||||||
{
|
{
|
||||||
return $this->data[self::APP_ENV] ?? self::ENV_PRODUCTION;
|
return $this->data[self::APP_ENV] ?? self::ENV_PRODUCTION;
|
||||||
|
@ -121,7 +135,7 @@ class Environment
|
||||||
|
|
||||||
public function isDocker(): bool
|
public function isDocker(): bool
|
||||||
{
|
{
|
||||||
return (bool)($this->data[self::IS_DOCKER] ?? true);
|
return $this->envToBool($this->data[self::IS_DOCKER] ?? true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isCli(): bool
|
public function isCli(): bool
|
||||||
|
@ -284,6 +298,11 @@ class Environment
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function enableRedis(): bool
|
||||||
|
{
|
||||||
|
return $this->envToBool($this->data[self::ENABLE_REDIS] ?? true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed[]
|
* @return mixed[]
|
||||||
*/
|
*/
|
||||||
|
@ -298,12 +317,12 @@ class Environment
|
||||||
|
|
||||||
public function isProfilingExtensionEnabled(): bool
|
public function isProfilingExtensionEnabled(): bool
|
||||||
{
|
{
|
||||||
return (1 === (int)($this->data[self::PROFILING_EXTENSION_ENABLED] ?? 0));
|
return $this->envToBool($this->data[self::PROFILING_EXTENSION_ENABLED] ?? false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isProfilingExtensionAlwaysOn(): bool
|
public function isProfilingExtensionAlwaysOn(): bool
|
||||||
{
|
{
|
||||||
return (1 === (int)($this->data[self::PROFILING_EXTENSION_ALWAYS_ON] ?? 0));
|
return $this->envToBool($this->data[self::PROFILING_EXTENSION_ALWAYS_ON] ?? false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getProfilingExtensionHttpKey(): string
|
public function getProfilingExtensionHttpKey(): string
|
||||||
|
|
|
@ -3,51 +3,44 @@
|
||||||
namespace App;
|
namespace App;
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Redis;
|
use Symfony\Component\Lock\BlockingStoreInterface;
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\LockFactory as SymfonyLockFactory;
|
||||||
use Symfony\Component\Lock\Lock;
|
|
||||||
use Symfony\Component\Lock\LockInterface;
|
use Symfony\Component\Lock\LockInterface;
|
||||||
use Symfony\Component\Lock\Store\RedisStore;
|
use Symfony\Component\Lock\PersistingStoreInterface;
|
||||||
use Symfony\Component\Lock\Store\RetryTillSaveStore;
|
use Symfony\Component\Lock\Store\RetryTillSaveStore;
|
||||||
|
|
||||||
class LockFactory
|
class LockFactory extends SymfonyLockFactory
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected Redis $redis,
|
protected Environment $environment,
|
||||||
protected LoggerInterface $logger
|
PersistingStoreInterface $lockStore,
|
||||||
|
LoggerInterface $logger
|
||||||
) {
|
) {
|
||||||
|
if (!$lockStore instanceof BlockingStoreInterface) {
|
||||||
|
$lockStore = new RetryTillSaveStore($lockStore, 30, 1000);
|
||||||
|
$lockStore->setLogger($logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($lockStore);
|
||||||
|
$this->setLogger($logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createLock(
|
public function createLock(string $resource, ?float $ttl = 300.0, bool $autoRelease = true): LockInterface
|
||||||
string $resource,
|
{
|
||||||
?float $ttl = 300.0,
|
return parent::createLock($this->getPrefixedResourceName($resource), $ttl, $autoRelease);
|
||||||
bool $autoRelease = true,
|
|
||||||
int $retrySleep = 1000,
|
|
||||||
int $retryCount = 30
|
|
||||||
): LockInterface {
|
|
||||||
$store = new RedisStore($this->redis);
|
|
||||||
$store = new RetryTillSaveStore($store, $retrySleep, $retryCount);
|
|
||||||
$store->setLogger($this->logger);
|
|
||||||
|
|
||||||
$lock = new Lock(new Key($this->getPrefixedResourceName($resource)), $store, $ttl, $autoRelease);
|
|
||||||
$lock->setLogger($this->logger);
|
|
||||||
|
|
||||||
return $lock;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createAndAcquireLock(
|
public function createAndAcquireLock(
|
||||||
string $resource,
|
string $resource,
|
||||||
?float $ttl = 300.0,
|
?float $ttl = 300.0,
|
||||||
bool $autoRelease = true,
|
bool $autoRelease = true,
|
||||||
int $retrySleep = 1000,
|
|
||||||
int $retryCount = 30,
|
|
||||||
bool $force = false
|
bool $force = false
|
||||||
): LockInterface|bool {
|
): LockInterface|bool {
|
||||||
$lock = $this->createLock($resource, $ttl, $autoRelease, $retrySleep, $retryCount);
|
$lock = $this->createLock($resource, $ttl, $autoRelease);
|
||||||
|
|
||||||
if ($force) {
|
if ($force) {
|
||||||
$this->clearQueue($resource);
|
|
||||||
try {
|
try {
|
||||||
|
$lock->release();
|
||||||
$lock->acquire(true);
|
$lock->acquire(true);
|
||||||
} catch (\Exception) {
|
} catch (\Exception) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -59,11 +52,6 @@ class LockFactory
|
||||||
return $lock;
|
return $lock;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clearQueue(string $resource): void
|
|
||||||
{
|
|
||||||
$this->redis->del($this->getPrefixedResourceName($resource));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getPrefixedResourceName(string $resource): string
|
protected function getPrefixedResourceName(string $resource): string
|
||||||
{
|
{
|
||||||
return 'lock_' . $resource;
|
return 'lock_' . $resource;
|
||||||
|
|
|
@ -13,15 +13,15 @@ class RateLimit
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected string $rl_group = 'default',
|
protected string $rl_group = 'default',
|
||||||
protected int $rl_timeout = 5,
|
protected int $rl_interval = 5,
|
||||||
protected int $rl_interval = 2
|
protected int $rl_limit = 2
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __invoke(ServerRequest $request, RequestHandlerInterface $handler): ResponseInterface
|
public function __invoke(ServerRequest $request, RequestHandlerInterface $handler): ResponseInterface
|
||||||
{
|
{
|
||||||
$rateLimit = $request->getRateLimit();
|
$rateLimit = $request->getRateLimit();
|
||||||
$rateLimit->checkRequestRateLimit($request, $this->rl_group, $this->rl_timeout, $this->rl_interval);
|
$rateLimit->checkRequestRateLimit($request, $this->rl_group, $this->rl_interval, $this->rl_limit);
|
||||||
|
|
||||||
return $handler->handle($request);
|
return $handler->handle($request);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,71 +3,73 @@
|
||||||
namespace App;
|
namespace App;
|
||||||
|
|
||||||
use App\Http\ServerRequest;
|
use App\Http\ServerRequest;
|
||||||
use Redis;
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
|
use Symfony\Component\Cache\Adapter\ProxyAdapter;
|
||||||
|
use Symfony\Component\RateLimiter\RateLimiterFactory;
|
||||||
|
use Symfony\Component\RateLimiter\Storage\CacheStorage;
|
||||||
|
|
||||||
class RateLimit
|
class RateLimit
|
||||||
{
|
{
|
||||||
|
protected CacheItemPoolInterface $psr6Cache;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected Redis $redis,
|
protected LockFactory $lockFactory,
|
||||||
protected Environment $environment
|
protected Environment $environment,
|
||||||
|
CacheItemPoolInterface $cacheItemPool
|
||||||
) {
|
) {
|
||||||
|
$this->psr6Cache = new ProxyAdapter($cacheItemPool, 'ratelimit.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ServerRequest $request
|
* @param ServerRequest $request
|
||||||
* @param string $groupName
|
* @param string $groupName
|
||||||
* @param int $timeout
|
|
||||||
* @param int $interval
|
* @param int $interval
|
||||||
|
* @param int $limit
|
||||||
*
|
*
|
||||||
* @throws Exception\RateLimitExceededException
|
* @throws Exception\RateLimitExceededException
|
||||||
*/
|
*/
|
||||||
public function checkRequestRateLimit(
|
public function checkRequestRateLimit(
|
||||||
ServerRequest $request,
|
ServerRequest $request,
|
||||||
string $groupName,
|
string $groupName,
|
||||||
int $timeout = 5,
|
int $interval = 5,
|
||||||
int $interval = 2
|
int $limit = 2
|
||||||
): void {
|
): void {
|
||||||
if ($this->environment->isTesting() || $this->environment->isCli()) {
|
if ($this->environment->isTesting() || $this->environment->isCli()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$ip = $request->getIp();
|
$ipKey = str_replace([':', '.'], '_', $request->getIp());
|
||||||
$cacheName = sprintf(
|
$this->checkRateLimit($groupName, $ipKey, $interval, $limit);
|
||||||
'%s.%s',
|
|
||||||
$groupName,
|
|
||||||
str_replace(':', '.', $ip)
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->checkRateLimit($cacheName, $timeout, $interval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $groupName
|
* @param string $groupName
|
||||||
* @param int $timeout
|
* @param string $key
|
||||||
* @param int $interval
|
* @param int $interval
|
||||||
|
* @param int $limit
|
||||||
*
|
*
|
||||||
* @throws Exception\RateLimitExceededException
|
* @throws Exception\RateLimitExceededException
|
||||||
*/
|
*/
|
||||||
public function checkRateLimit(
|
public function checkRateLimit(
|
||||||
string $groupName,
|
string $groupName,
|
||||||
int $timeout = 5,
|
string $key,
|
||||||
int $interval = 2
|
int $interval = 5,
|
||||||
|
int $limit = 2
|
||||||
): void {
|
): void {
|
||||||
$cacheName = sprintf(
|
$cacheStore = new CacheStorage($this->psr6Cache);
|
||||||
'rate_limit.%s',
|
|
||||||
$groupName
|
|
||||||
);
|
|
||||||
|
|
||||||
$result = $this->redis->get($cacheName);
|
$config = [
|
||||||
|
'id' => 'ratelimit.' . $groupName,
|
||||||
|
'policy' => 'sliding_window',
|
||||||
|
'interval' => $interval . ' seconds',
|
||||||
|
'limit' => $limit,
|
||||||
|
];
|
||||||
|
|
||||||
if ($result !== false) {
|
$rateLimiterFactory = new RateLimiterFactory($config, $cacheStore, $this->lockFactory);
|
||||||
if ((int)$result + 1 > $interval) {
|
$rateLimiter = $rateLimiterFactory->create($key);
|
||||||
throw new Exception\RateLimitExceededException();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->redis->incr($cacheName);
|
if (false === $rateLimiter->consume(1)->isAccepted()) {
|
||||||
} else {
|
throw new Exception\RateLimitExceededException();
|
||||||
$this->redis->setex($cacheName, $timeout, 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,9 +49,7 @@ class LastFm
|
||||||
$rateLimitLock = $this->lockFactory->createLock(
|
$rateLimitLock = $this->lockFactory->createLock(
|
||||||
'api_lastfm',
|
'api_lastfm',
|
||||||
1,
|
1,
|
||||||
false,
|
false
|
||||||
500,
|
|
||||||
10
|
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -39,9 +39,7 @@ class MusicBrainz
|
||||||
$rateLimitLock = $this->lockFactory->createLock(
|
$rateLimitLock = $this->lockFactory->createLock(
|
||||||
'api_musicbrainz',
|
'api_musicbrainz',
|
||||||
1,
|
1,
|
||||||
false,
|
false
|
||||||
500,
|
|
||||||
10
|
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
{{if eq .Env.ENABLE_REDIS "true"}}
|
||||||
upstream redis_server {
|
upstream redis_server {
|
||||||
nchan_redis_server "redis://redis:6379";
|
nchan_redis_server "redis://redis:6379";
|
||||||
}
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
resolver 127.0.0.11;
|
resolver 127.0.0.11;
|
||||||
|
|
||||||
|
@ -50,7 +52,9 @@ server {
|
||||||
|
|
||||||
location ~ /pub/(\w+)$ {
|
location ~ /pub/(\w+)$ {
|
||||||
nchan_publisher;
|
nchan_publisher;
|
||||||
|
{{if eq .Env.ENABLE_REDIS "true"}}
|
||||||
nchan_redis_pass redis_server;
|
nchan_redis_pass redis_server;
|
||||||
|
{{end}}
|
||||||
|
|
||||||
nchan_channel_group "azuracast_nowplaying";
|
nchan_channel_group "azuracast_nowplaying";
|
||||||
nchan_channel_id $1;
|
nchan_channel_id $1;
|
||||||
|
@ -152,7 +156,9 @@ server {
|
||||||
nchan_access_control_allow_origin "*";
|
nchan_access_control_allow_origin "*";
|
||||||
|
|
||||||
nchan_subscriber;
|
nchan_subscriber;
|
||||||
|
{{if eq .Env.ENABLE_REDIS "true"}}
|
||||||
nchan_redis_pass redis_server;
|
nchan_redis_pass redis_server;
|
||||||
|
{{end}}
|
||||||
|
|
||||||
nchan_channel_group "azuracast_nowplaying";
|
nchan_channel_group "azuracast_nowplaying";
|
||||||
nchan_channel_id "$1";
|
nchan_channel_id "$1";
|
||||||
|
|
|
@ -1,4 +1,20 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
bool() {
|
||||||
|
case "$1" in
|
||||||
|
Y* | y* | true | TRUE | 1) return 0 ;;
|
||||||
|
esac
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ENABLE_REDIS=${ENABLE_REDIS:-false}
|
||||||
|
if bool "$ENABLE_REDIS"; then
|
||||||
|
ENABLE_REDIS=true
|
||||||
|
else
|
||||||
|
ENABLE_REDIS=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
export ENABLE_REDIS
|
||||||
|
|
||||||
# Copy the nginx template to its destination.
|
# Copy the nginx template to its destination.
|
||||||
dockerize -template "/etc/nginx/azuracast.conf.tmpl:/etc/nginx/conf.d/azuracast.conf"
|
dockerize -template "/etc/nginx/azuracast.conf.tmpl:/etc/nginx/conf.d/azuracast.conf"
|
||||||
|
|
Loading…
Reference in a new issue