mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-25 18:45:27 -06:00
@@ -18,6 +18,7 @@ matrix:
|
|||||||
before_install:
|
before_install:
|
||||||
- echo 'extension = memcached.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
- echo 'extension = memcached.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
||||||
- echo 'extension = apcu.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
- echo 'extension = apcu.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
||||||
|
- yes | pecl install swoole
|
||||||
- phpenv config-rm xdebug.ini || return 0
|
- phpenv config-rm xdebug.ini || return 0
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
|
|
||||||
#### Added
|
#### Added
|
||||||
|
|
||||||
* *Nothing*
|
* [#208](https://github.com/shlinkio/shlink/issues/208) Added initial support to run shlink using [swoole](https://www.swoole.co.uk/), a non-blocking IO server which improves the performance of shlink from 4 to 10 times.
|
||||||
|
|
||||||
|
Run shlink with `./vendor/bin/zend-expressive-swoole start` to start-up the service, which will be exposed in port `8080`.
|
||||||
|
|
||||||
|
Adding the `-d` flag, it will be started as a background service. Then you can use the `./vendor/bin/zend-expressive-swoole stop` command in order to stop it.
|
||||||
|
|
||||||
#### Changed
|
#### Changed
|
||||||
|
|
||||||
|
|||||||
3
bin/cli
3
bin/cli
@@ -3,8 +3,11 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Interop\Container\ContainerInterface;
|
use Interop\Container\ContainerInterface;
|
||||||
|
use Shlinkio\Shlink\Common\Exec\ExecutionContext;
|
||||||
use Symfony\Component\Console\Application as CliApp;
|
use Symfony\Component\Console\Application as CliApp;
|
||||||
|
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = include __DIR__ . '/../config/container.php';
|
$container = include __DIR__ . '/../config/container.php';
|
||||||
|
|
||||||
|
putenv(sprintf('CURRENT_SHLINK_CONTEXT=%s', ExecutionContext::CLI));
|
||||||
$container->get(CliApp::class)->run();
|
$container->get(CliApp::class)->run();
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"zendframework/zend-expressive-fastroute": "^3.0",
|
"zendframework/zend-expressive-fastroute": "^3.0",
|
||||||
"zendframework/zend-expressive-helpers": "^5.0",
|
"zendframework/zend-expressive-helpers": "^5.0",
|
||||||
"zendframework/zend-expressive-platesrenderer": "^2.0",
|
"zendframework/zend-expressive-platesrenderer": "^2.0",
|
||||||
|
"zendframework/zend-expressive-swoole": "^2.0",
|
||||||
"zendframework/zend-i18n": "^2.7",
|
"zendframework/zend-i18n": "^2.7",
|
||||||
"zendframework/zend-inputfilter": "^2.8",
|
"zendframework/zend-inputfilter": "^2.8",
|
||||||
"zendframework/zend-paginator": "^2.6",
|
"zendframework/zend-paginator": "^2.6",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Shlinkio\Shlink\Common;
|
use function Shlinkio\Shlink\Common\env;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
@@ -10,9 +10,9 @@ return [
|
|||||||
'proxies_dir' => 'data/proxies',
|
'proxies_dir' => 'data/proxies',
|
||||||
],
|
],
|
||||||
'connection' => [
|
'connection' => [
|
||||||
'user' => Common\env('DB_USER'),
|
'user' => env('DB_USER'),
|
||||||
'password' => Common\env('DB_PASSWORD'),
|
'password' => env('DB_PASSWORD'),
|
||||||
'dbname' => Common\env('DB_NAME', 'shlink'),
|
'dbname' => env('DB_NAME', 'shlink'),
|
||||||
'charset' => 'utf8',
|
'charset' => 'utf8',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ declare(strict_types=1);
|
|||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Monolog\Handler\RotatingFileHandler;
|
use Monolog\Handler\RotatingFileHandler;
|
||||||
|
use Monolog\Handler\StreamHandler;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use Monolog\Processor;
|
use Monolog\Processor;
|
||||||
|
use Zend\Expressive\Swoole\Log\AccessLogInterface;
|
||||||
use const PHP_EOL;
|
use const PHP_EOL;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -19,13 +21,19 @@ return [
|
|||||||
],
|
],
|
||||||
|
|
||||||
'handlers' => [
|
'handlers' => [
|
||||||
'rotating_file_handler' => [
|
'shlink_rotating_handler' => [
|
||||||
'class' => RotatingFileHandler::class,
|
'class' => RotatingFileHandler::class,
|
||||||
'level' => Logger::INFO,
|
'level' => Logger::INFO,
|
||||||
'filename' => 'data/log/shlink_log.log',
|
'filename' => 'data/log/shlink_log.log',
|
||||||
'max_files' => 30,
|
'max_files' => 30,
|
||||||
'formatter' => 'dashed',
|
'formatter' => 'dashed',
|
||||||
],
|
],
|
||||||
|
'swoole_access_handler' => [
|
||||||
|
'class' => StreamHandler::class,
|
||||||
|
'level' => Logger::INFO,
|
||||||
|
'stream' => 'php://stdout',
|
||||||
|
'formatter' => 'dashed',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'processors' => [
|
'processors' => [
|
||||||
@@ -39,9 +47,30 @@ return [
|
|||||||
|
|
||||||
'loggers' => [
|
'loggers' => [
|
||||||
'Shlink' => [
|
'Shlink' => [
|
||||||
'handlers' => ['rotating_file_handler'],
|
'handlers' => ['shlink_rotating_handler'],
|
||||||
'processors' => ['exception_with_new_line', 'psr3'],
|
'processors' => ['exception_with_new_line', 'psr3'],
|
||||||
],
|
],
|
||||||
|
'Swoole' => [
|
||||||
|
'handlers' => ['swoole_access_handler'],
|
||||||
|
'processors' => ['psr3'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'dependencies' => [
|
||||||
|
'factories' => [
|
||||||
|
'Logger_Shlink' => Common\Factory\LoggerFactory::class,
|
||||||
|
'Logger_Swoole' => Common\Factory\LoggerFactory::class,
|
||||||
|
|
||||||
|
AccessLogInterface::class => Common\Logger\Swoole\AccessLogFactory::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'zend-expressive-swoole' => [
|
||||||
|
'swoole-http-server' => [
|
||||||
|
'logger' => [
|
||||||
|
'logger_name' => 'Logger_Swoole',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'handlers' => [
|
'handlers' => [
|
||||||
'rotating_file_handler' => [
|
'shlink_rotating_handler' => [
|
||||||
'level' => Logger::DEBUG,
|
'level' => Logger::DEBUG,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -10,10 +10,18 @@ return [
|
|||||||
|
|
||||||
'middleware_pipeline' => [
|
'middleware_pipeline' => [
|
||||||
'pre-routing' => [
|
'pre-routing' => [
|
||||||
'middleware' => [
|
'middleware' => (function () {
|
||||||
ErrorHandler::class,
|
$middleware = [
|
||||||
Expressive\Helper\ContentLengthMiddleware::class,
|
ErrorHandler::class,
|
||||||
],
|
Expressive\Helper\ContentLengthMiddleware::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (Common\Exec\ExecutionContext::currentContextIsSwoole()) {
|
||||||
|
$middleware[] = Common\Middleware\CloseDbConnectionMiddleware::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $middleware;
|
||||||
|
})(),
|
||||||
'priority' => 12,
|
'priority' => 12,
|
||||||
],
|
],
|
||||||
'pre-routing-rest' => [
|
'pre-routing-rest' => [
|
||||||
|
|||||||
14
config/autoload/swoole.global.php
Normal file
14
config/autoload/swoole.global.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'zend-expressive-swoole' => [
|
||||||
|
'enable_coroutine' => true,
|
||||||
|
|
||||||
|
'swoole-http-server' => [
|
||||||
|
'host' => '0.0.0.0',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
@@ -6,7 +6,6 @@ namespace Shlinkio\Shlink;
|
|||||||
use Acelaya\ExpressiveErrorHandler;
|
use Acelaya\ExpressiveErrorHandler;
|
||||||
use Zend\ConfigAggregator;
|
use Zend\ConfigAggregator;
|
||||||
use Zend\Expressive;
|
use Zend\Expressive;
|
||||||
use function class_exists;
|
|
||||||
|
|
||||||
return (new ConfigAggregator\ConfigAggregator([
|
return (new ConfigAggregator\ConfigAggregator([
|
||||||
Expressive\ConfigProvider::class,
|
Expressive\ConfigProvider::class,
|
||||||
@@ -14,9 +13,7 @@ return (new ConfigAggregator\ConfigAggregator([
|
|||||||
Expressive\Router\FastRouteRouter\ConfigProvider::class,
|
Expressive\Router\FastRouteRouter\ConfigProvider::class,
|
||||||
Expressive\Plates\ConfigProvider::class,
|
Expressive\Plates\ConfigProvider::class,
|
||||||
Expressive\Helper\ConfigProvider::class,
|
Expressive\Helper\ConfigProvider::class,
|
||||||
class_exists(Expressive\Swoole\ConfigProvider::class)
|
Expressive\Swoole\ConfigProvider::class,
|
||||||
? Expressive\Swoole\ConfigProvider::class
|
|
||||||
: new ConfigAggregator\ArrayProvider([]),
|
|
||||||
ExpressiveErrorHandler\ConfigProvider::class,
|
ExpressiveErrorHandler\ConfigProvider::class,
|
||||||
Common\ConfigProvider::class,
|
Common\ConfigProvider::class,
|
||||||
Core\ConfigProvider::class,
|
Core\ConfigProvider::class,
|
||||||
|
|||||||
6
config/pipeline.php
Normal file
6
config/pipeline.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
// FIXME Dummy file just to prevent expressive-swoole fail while loading
|
||||||
|
return function () {
|
||||||
|
};
|
||||||
6
config/routes.php
Normal file
6
config/routes.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
// FIXME Dummy file just to prevent expressive-swoole fail while loading
|
||||||
|
return function () {
|
||||||
|
};
|
||||||
98
data/infra/swoole.Dockerfile
Normal file
98
data/infra/swoole.Dockerfile
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
FROM php:7.1.22-cli-alpine3.7
|
||||||
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
|
RUN apk update
|
||||||
|
|
||||||
|
# Install common php extensions
|
||||||
|
RUN docker-php-ext-install pdo_mysql
|
||||||
|
RUN docker-php-ext-install iconv
|
||||||
|
RUN docker-php-ext-install mbstring
|
||||||
|
RUN docker-php-ext-install calendar
|
||||||
|
|
||||||
|
RUN apk add --no-cache --virtual sqlite-libs
|
||||||
|
RUN apk add --no-cache --virtual sqlite-dev
|
||||||
|
RUN docker-php-ext-install pdo_sqlite
|
||||||
|
|
||||||
|
RUN apk add --no-cache --virtual icu-dev
|
||||||
|
RUN docker-php-ext-install intl
|
||||||
|
|
||||||
|
RUN apk add --no-cache --virtual zlib-dev
|
||||||
|
RUN docker-php-ext-install zip
|
||||||
|
|
||||||
|
RUN apk add --no-cache --virtual libmcrypt-dev
|
||||||
|
RUN docker-php-ext-install mcrypt
|
||||||
|
|
||||||
|
RUN apk add --no-cache --virtual libpng-dev
|
||||||
|
RUN docker-php-ext-install gd
|
||||||
|
|
||||||
|
# Install redis extension
|
||||||
|
ADD https://github.com/phpredis/phpredis/archive/3.1.4.tar.gz /tmp/phpredis.tar.gz
|
||||||
|
RUN mkdir -p /usr/src/php/ext/redis\
|
||||||
|
&& tar xf /tmp/phpredis.tar.gz -C /usr/src/php/ext/redis --strip-components=1
|
||||||
|
# configure and install
|
||||||
|
RUN docker-php-ext-configure redis\
|
||||||
|
&& docker-php-ext-install redis
|
||||||
|
# cleanup
|
||||||
|
RUN rm /tmp/phpredis.tar.gz
|
||||||
|
|
||||||
|
# Install memcached extension
|
||||||
|
RUN apk add --no-cache --virtual cyrus-sasl-dev
|
||||||
|
RUN apk add --no-cache --virtual libmemcached-dev
|
||||||
|
ADD https://github.com/php-memcached-dev/php-memcached/archive/php7.tar.gz /tmp/memcached.tar.gz
|
||||||
|
RUN mkdir -p /usr/src/php/ext/memcached\
|
||||||
|
&& tar xf /tmp/memcached.tar.gz -C /usr/src/php/ext/memcached --strip-components=1
|
||||||
|
# configure and install
|
||||||
|
RUN docker-php-ext-configure memcached\
|
||||||
|
&& docker-php-ext-install memcached
|
||||||
|
# cleanup
|
||||||
|
RUN rm /tmp/memcached.tar.gz
|
||||||
|
|
||||||
|
# Install APCu extension
|
||||||
|
ADD https://pecl.php.net/get/apcu-5.1.3.tgz /tmp/apcu.tar.gz
|
||||||
|
RUN mkdir -p /usr/src/php/ext/apcu\
|
||||||
|
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1
|
||||||
|
# configure and install
|
||||||
|
RUN docker-php-ext-configure apcu\
|
||||||
|
&& docker-php-ext-install apcu
|
||||||
|
# cleanup
|
||||||
|
RUN rm /tmp/apcu.tar.gz
|
||||||
|
|
||||||
|
# Install APCu-BC extension
|
||||||
|
ADD https://pecl.php.net/get/apcu_bc-1.0.3.tgz /tmp/apcu_bc.tar.gz
|
||||||
|
RUN mkdir -p /usr/src/php/ext/apcu-bc\
|
||||||
|
&& tar xf /tmp/apcu_bc.tar.gz -C /usr/src/php/ext/apcu-bc --strip-components=1
|
||||||
|
# configure and install
|
||||||
|
RUN docker-php-ext-configure apcu-bc\
|
||||||
|
&& docker-php-ext-install apcu-bc
|
||||||
|
# cleanup
|
||||||
|
RUN rm /tmp/apcu_bc.tar.gz
|
||||||
|
|
||||||
|
# Load APCU.ini before APC.ini
|
||||||
|
RUN rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini
|
||||||
|
RUN echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||||
|
|
||||||
|
# Install swoole
|
||||||
|
# First line fixes an error when installing pecl extensions. Found in https://github.com/docker-library/php/issues/233
|
||||||
|
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS && \
|
||||||
|
pecl install swoole && \
|
||||||
|
docker-php-ext-enable swoole && \
|
||||||
|
apk del .phpize-deps
|
||||||
|
|
||||||
|
# Install composer
|
||||||
|
RUN php -r "readfile('https://getcomposer.org/installer');" | php
|
||||||
|
RUN chmod +x composer.phar
|
||||||
|
RUN mv composer.phar /usr/local/bin/composer
|
||||||
|
|
||||||
|
# Make home directory writable by anyone
|
||||||
|
RUN chmod 777 /home
|
||||||
|
|
||||||
|
VOLUME /home/shlink
|
||||||
|
WORKDIR /home/shlink
|
||||||
|
|
||||||
|
# Expose swoole port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD /usr/local/bin/composer update && \
|
||||||
|
# When restarting the container, swoole might think it is already in execution
|
||||||
|
# This forces the app to be started every second until the exit code is 0
|
||||||
|
until php ./vendor/bin/zend-expressive-swoole start; do sleep 1 ; done
|
||||||
@@ -6,3 +6,9 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /etc/passwd:/etc/passwd:ro
|
- /etc/passwd:/etc/passwd:ro
|
||||||
- /etc/group:/etc/group:ro
|
- /etc/group:/etc/group:ro
|
||||||
|
|
||||||
|
shlink_swoole:
|
||||||
|
user: 1000:1000
|
||||||
|
volumes:
|
||||||
|
- /etc/passwd:/etc/passwd:ro
|
||||||
|
- /etc/group:/etc/group:ro
|
||||||
|
|||||||
@@ -26,6 +26,18 @@ services:
|
|||||||
links:
|
links:
|
||||||
- shlink_db
|
- shlink_db
|
||||||
|
|
||||||
|
shlink_swoole:
|
||||||
|
container_name: shlink_swoole
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./data/infra/swoole.Dockerfile
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- ./:/home/shlink
|
||||||
|
links:
|
||||||
|
- shlink_db
|
||||||
|
|
||||||
shlink_db:
|
shlink_db:
|
||||||
container_name: shlink_db
|
container_name: shlink_db
|
||||||
build:
|
build:
|
||||||
|
|||||||
2
indocker
2
indocker
@@ -1,2 +1,2 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
docker exec -it shlink_php /bin/sh -c "cd /home/shlink/www && $*"
|
docker exec -it shlink_swoole /bin/sh -c "$*"
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ return [
|
|||||||
EntityManager::class => Factory\EntityManagerFactory::class,
|
EntityManager::class => Factory\EntityManagerFactory::class,
|
||||||
GuzzleClient::class => InvokableFactory::class,
|
GuzzleClient::class => InvokableFactory::class,
|
||||||
Cache::class => Factory\CacheFactory::class,
|
Cache::class => Factory\CacheFactory::class,
|
||||||
'Logger_Shlink' => Factory\LoggerFactory::class,
|
|
||||||
Filesystem::class => InvokableFactory::class,
|
Filesystem::class => InvokableFactory::class,
|
||||||
Reader::class => ConfigAbstractFactory::class,
|
Reader::class => ConfigAbstractFactory::class,
|
||||||
|
|
||||||
@@ -31,6 +30,7 @@ return [
|
|||||||
Template\Extension\TranslatorExtension::class => ConfigAbstractFactory::class,
|
Template\Extension\TranslatorExtension::class => ConfigAbstractFactory::class,
|
||||||
|
|
||||||
Middleware\LocaleMiddleware::class => ConfigAbstractFactory::class,
|
Middleware\LocaleMiddleware::class => ConfigAbstractFactory::class,
|
||||||
|
Middleware\CloseDbConnectionMiddleware::class => ConfigAbstractFactory::class,
|
||||||
IpAddress::class => Middleware\IpAddressMiddlewareFactory::class,
|
IpAddress::class => Middleware\IpAddressMiddlewareFactory::class,
|
||||||
|
|
||||||
Image\ImageBuilder::class => Image\ImageBuilderFactory::class,
|
Image\ImageBuilder::class => Image\ImageBuilderFactory::class,
|
||||||
@@ -78,6 +78,7 @@ return [
|
|||||||
|
|
||||||
Template\Extension\TranslatorExtension::class => ['translator'],
|
Template\Extension\TranslatorExtension::class => ['translator'],
|
||||||
Middleware\LocaleMiddleware::class => ['translator'],
|
Middleware\LocaleMiddleware::class => ['translator'],
|
||||||
|
Middleware\CloseDbConnectionMiddleware::class => ['em'],
|
||||||
|
|
||||||
IpGeolocation\IpApiLocationResolver::class => ['httpClient'],
|
IpGeolocation\IpApiLocationResolver::class => ['httpClient'],
|
||||||
IpGeolocation\GeoLite2LocationResolver::class => [Reader::class],
|
IpGeolocation\GeoLite2LocationResolver::class => [Reader::class],
|
||||||
|
|||||||
18
module/Common/src/Exec/ExecutionContext.php
Normal file
18
module/Common/src/Exec/ExecutionContext.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Common\Exec;
|
||||||
|
|
||||||
|
use const PHP_SAPI;
|
||||||
|
use function Shlinkio\Shlink\Common\env;
|
||||||
|
|
||||||
|
abstract class ExecutionContext
|
||||||
|
{
|
||||||
|
public const WEB = 'shlink_web';
|
||||||
|
public const CLI = 'shlink_cli';
|
||||||
|
|
||||||
|
public static function currentContextIsSwoole(): bool
|
||||||
|
{
|
||||||
|
return PHP_SAPI === 'cli' && env('CURRENT_SHLINK_CONTEXT', self::WEB) === self::WEB;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,6 @@ class TranslatorFactory implements FactoryInterface
|
|||||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||||
{
|
{
|
||||||
$config = $container->get('config');
|
$config = $container->get('config');
|
||||||
return Translator::factory(isset($config['translator']) ? $config['translator'] : []);
|
return Translator::factory($config['translator'] ?? []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
51
module/Common/src/Logger/Swoole/AccessLogFactory.php
Normal file
51
module/Common/src/Logger/Swoole/AccessLogFactory.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Common\Logger\Swoole;
|
||||||
|
|
||||||
|
use Interop\Container\ContainerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Zend\Expressive\Swoole\Log;
|
||||||
|
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||||
|
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||||
|
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||||
|
|
||||||
|
class AccessLogFactory implements FactoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create an object
|
||||||
|
*
|
||||||
|
* @param ContainerInterface $container
|
||||||
|
* @param string $requestedName
|
||||||
|
* @param null|array $options
|
||||||
|
* @return object
|
||||||
|
* @throws ServiceNotFoundException if unable to resolve the service.
|
||||||
|
* @throws ServiceNotCreatedException if an exception is raised when creating a service.
|
||||||
|
*/
|
||||||
|
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||||
|
{
|
||||||
|
$config = $container->has('config') ? $container->get('config') : [];
|
||||||
|
$config = $config['zend-expressive-swoole']['swoole-http-server']['logger'] ?? [];
|
||||||
|
|
||||||
|
return new Log\Psr3AccessLogDecorator(
|
||||||
|
$this->getLogger($container, $config),
|
||||||
|
$this->getFormatter($container, $config),
|
||||||
|
$config['use-hostname-lookups'] ?? false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getLogger(ContainerInterface $container, array $config): LoggerInterface
|
||||||
|
{
|
||||||
|
$loggerName = $config['logger_name'] ?? LoggerInterface::class;
|
||||||
|
return $container->has($loggerName) ? $container->get($loggerName) : new Log\StdoutLogger();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFormatter(ContainerInterface $container, array $config): Log\AccessLogFormatterInterface
|
||||||
|
{
|
||||||
|
if ($container->has(Log\AccessLogFormatterInterface::class)) {
|
||||||
|
return $container->get(Log\AccessLogFormatterInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Log\AccessLogFormatter($config['format'] ?? Log\AccessLogFormatter::FORMAT_COMMON);
|
||||||
|
}
|
||||||
|
}
|
||||||
34
module/Common/src/Middleware/CloseDbConnectionMiddleware.php
Normal file
34
module/Common/src/Middleware/CloseDbConnectionMiddleware.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Common\Middleware;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
|
||||||
|
class CloseDbConnectionMiddleware implements MiddlewareInterface
|
||||||
|
{
|
||||||
|
/** @var EntityManagerInterface */
|
||||||
|
private $em;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
$this->em = $em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process an incoming server request and return a response, optionally delegating
|
||||||
|
* response creation to a handler.
|
||||||
|
*/
|
||||||
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||||
|
{
|
||||||
|
$handledRequest = $handler->handle($request);
|
||||||
|
$this->em->getConnection()->close();
|
||||||
|
$this->em->clear();
|
||||||
|
|
||||||
|
return $handledRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
138
module/Common/test/Logger/Swoole/AccessLogFactoryTest.php
Normal file
138
module/Common/test/Logger/Swoole/AccessLogFactoryTest.php
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ShlinkioTest\Shlink\Common\Logger\Swoole;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Psr\Log\NullLogger;
|
||||||
|
use ReflectionObject;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\Swoole\AccessLogFactory;
|
||||||
|
use Zend\Expressive\Swoole\Log\AccessLogFormatter;
|
||||||
|
use Zend\Expressive\Swoole\Log\AccessLogFormatterInterface;
|
||||||
|
use Zend\Expressive\Swoole\Log\Psr3AccessLogDecorator;
|
||||||
|
use Zend\Expressive\Swoole\Log\StdoutLogger;
|
||||||
|
use Zend\ServiceManager\ServiceManager;
|
||||||
|
use function is_string;
|
||||||
|
|
||||||
|
class AccessLogFactoryTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @var AccessLogFactory */
|
||||||
|
private $factory;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->factory = new AccessLogFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function createsService()
|
||||||
|
{
|
||||||
|
$service = ($this->factory)(new ServiceManager(), '');
|
||||||
|
$this->assertInstanceOf(Psr3AccessLogDecorator::class, $service);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @dataProvider provideLoggers
|
||||||
|
* @param array $config
|
||||||
|
* @param string|LoggerInterface $expectedLogger
|
||||||
|
*/
|
||||||
|
public function wrapsProperLogger(array $config, $expectedLogger)
|
||||||
|
{
|
||||||
|
$service = ($this->factory)(new ServiceManager(['services' => $config]), '');
|
||||||
|
|
||||||
|
$ref = new ReflectionObject($service);
|
||||||
|
$loggerProp = $ref->getProperty('logger');
|
||||||
|
$loggerProp->setAccessible(true);
|
||||||
|
$logger = $loggerProp->getValue($service);
|
||||||
|
|
||||||
|
if (is_string($expectedLogger)) {
|
||||||
|
$this->assertInstanceOf($expectedLogger, $logger);
|
||||||
|
} else {
|
||||||
|
$this->assertSame($expectedLogger, $logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideLoggers(): iterable
|
||||||
|
{
|
||||||
|
yield 'without-any-logger' => [[], StdoutLogger::class];
|
||||||
|
yield 'with-standard-logger' => (function () {
|
||||||
|
$logger = new NullLogger();
|
||||||
|
return [[LoggerInterface::class => $logger], $logger];
|
||||||
|
})();
|
||||||
|
yield 'with-custom-logger' => (function () {
|
||||||
|
$logger = new NullLogger();
|
||||||
|
return [[
|
||||||
|
'config' => [
|
||||||
|
'zend-expressive-swoole' => [
|
||||||
|
'swoole-http-server' => [
|
||||||
|
'logger' => [
|
||||||
|
'logger_name' => 'my-logger',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'my-logger' => $logger,
|
||||||
|
], $logger];
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @dataProvider provideFormatters
|
||||||
|
* @param array $config
|
||||||
|
* @param string|AccessLogFormatterInterface $expectedFormatter
|
||||||
|
*/
|
||||||
|
public function wrappsProperFormatter(array $config, $expectedFormatter, string $expectedFormat)
|
||||||
|
{
|
||||||
|
$service = ($this->factory)(new ServiceManager(['services' => $config]), '');
|
||||||
|
|
||||||
|
$ref = new ReflectionObject($service);
|
||||||
|
$formatterProp = $ref->getProperty('formatter');
|
||||||
|
$formatterProp->setAccessible(true);
|
||||||
|
$formatter = $formatterProp->getValue($service);
|
||||||
|
|
||||||
|
$ref = new ReflectionObject($formatter);
|
||||||
|
$formatProp = $ref->getProperty('format');
|
||||||
|
$formatProp->setAccessible(true);
|
||||||
|
$format = $formatProp->getValue($formatter);
|
||||||
|
|
||||||
|
if (is_string($expectedFormatter)) {
|
||||||
|
$this->assertInstanceOf($expectedFormatter, $formatter);
|
||||||
|
} else {
|
||||||
|
$this->assertSame($expectedFormatter, $formatter);
|
||||||
|
}
|
||||||
|
$this->assertSame($expectedFormat, $format);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideFormatters(): iterable
|
||||||
|
{
|
||||||
|
yield 'with-registered-formatter-and-default-format' => (function () {
|
||||||
|
$formatter = new AccessLogFormatter();
|
||||||
|
return [[AccessLogFormatterInterface::class => $formatter], $formatter, AccessLogFormatter::FORMAT_COMMON];
|
||||||
|
})();
|
||||||
|
yield 'with-registered-formatter-and-custom-format' => (function () {
|
||||||
|
$formatter = new AccessLogFormatter(AccessLogFormatter::FORMAT_AGENT);
|
||||||
|
return [[AccessLogFormatterInterface::class => $formatter], $formatter, AccessLogFormatter::FORMAT_AGENT];
|
||||||
|
})();
|
||||||
|
yield 'with-no-formatter-and-not-configured-format' => [
|
||||||
|
[],
|
||||||
|
AccessLogFormatter::class,
|
||||||
|
AccessLogFormatter::FORMAT_COMMON,
|
||||||
|
];
|
||||||
|
yield 'with-no-formatter-and-configured-format' => [[
|
||||||
|
'config' => [
|
||||||
|
'zend-expressive-swoole' => [
|
||||||
|
'swoole-http-server' => [
|
||||||
|
'logger' => [
|
||||||
|
'format' => AccessLogFormatter::FORMAT_COMBINED_DEBIAN,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
], AccessLogFormatter::class, AccessLogFormatter::FORMAT_COMBINED_DEBIAN];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ShlinkioTest\Shlink\Common\Middleware;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Connection;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\CloseDbConnectionMiddleware;
|
||||||
|
use Zend\Diactoros\Response;
|
||||||
|
use Zend\Diactoros\ServerRequestFactory;
|
||||||
|
|
||||||
|
class CloseDbConnectionMiddlewareTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @var CloseDbConnectionMiddleware */
|
||||||
|
private $middleware;
|
||||||
|
/** @var ObjectProphecy */
|
||||||
|
private $handler;
|
||||||
|
/** @var ObjectProphecy */
|
||||||
|
private $em;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->handler = $this->prophesize(RequestHandlerInterface::class);
|
||||||
|
$this->em = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
$this->middleware = new CloseDbConnectionMiddleware($this->em->reveal());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function connectionIsClosedWhenMiddlewareIsProcessed()
|
||||||
|
{
|
||||||
|
$req = ServerRequestFactory::fromGlobals();
|
||||||
|
$resp = new Response();
|
||||||
|
|
||||||
|
$conn = $this->prophesize(Connection::class);
|
||||||
|
$closeConn = $conn->close()->will(function () {
|
||||||
|
});
|
||||||
|
$getConn = $this->em->getConnection()->willReturn($conn->reveal());
|
||||||
|
$clear = $this->em->clear()->will(function () {
|
||||||
|
});
|
||||||
|
$handle = $this->handler->handle($req)->willReturn($resp);
|
||||||
|
|
||||||
|
$result = $this->middleware->process($req, $this->handler->reveal());
|
||||||
|
|
||||||
|
$this->assertSame($result, $resp);
|
||||||
|
$getConn->shouldHaveBeenCalledOnce();
|
||||||
|
$closeConn->shouldHaveBeenCalledOnce();
|
||||||
|
$clear->shouldHaveBeenCalledOnce();
|
||||||
|
$handle->shouldHaveBeenCalledOnce();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -107,13 +107,7 @@ class UrlShortener implements UrlShortenerInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function checkUrlExists(UriInterface $url): void
|
||||||
* Tries to perform a GET request to provided url, returning true on success and false on failure
|
|
||||||
*
|
|
||||||
* @param UriInterface $url
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function checkUrlExists(UriInterface $url)
|
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->httpClient->request('GET', $url, ['allow_redirects' => [
|
$this->httpClient->request('GET', $url, ['allow_redirects' => [
|
||||||
@@ -124,12 +118,6 @@ class UrlShortener implements UrlShortenerInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the unique shortcode for an autoincrement ID
|
|
||||||
*
|
|
||||||
* @param float $id
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function convertAutoincrementIdToShortCode(float $id): string
|
private function convertAutoincrementIdToShortCode(float $id): string
|
||||||
{
|
{
|
||||||
$id += self::ID_INCREMENT; // Increment the Id so that the generated shortcode is not too short
|
$id += self::ID_INCREMENT; // Increment the Id so that the generated shortcode is not too short
|
||||||
@@ -145,7 +133,7 @@ class UrlShortener implements UrlShortenerInterface
|
|||||||
return $this->chars[(int) $id] . $code;
|
return $this->chars[(int) $id] . $code;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function processCustomSlug($customSlug)
|
private function processCustomSlug(?string $customSlug): ?string
|
||||||
{
|
{
|
||||||
if ($customSlug === null) {
|
if ($customSlug === null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -56,4 +56,5 @@
|
|||||||
<file>config</file>
|
<file>config</file>
|
||||||
<file>public/index.php</file>
|
<file>public/index.php</file>
|
||||||
<exclude-pattern>config/params/*</exclude-pattern>
|
<exclude-pattern>config/params/*</exclude-pattern>
|
||||||
|
<exclude-pattern>public/index.php</exclude-pattern>
|
||||||
</ruleset>
|
</ruleset>
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Shlinkio\Shlink\Common\Exec\ExecutionContext;
|
||||||
use Zend\Expressive\Application;
|
use Zend\Expressive\Application;
|
||||||
|
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = include __DIR__ . '/../config/container.php';
|
$container = include __DIR__ . '/../config/container.php';
|
||||||
|
|
||||||
|
putenv(sprintf('CURRENT_SHLINK_CONTEXT=%s', ExecutionContext::WEB));
|
||||||
$container->get(Application::class)->run();
|
$container->get(Application::class)->run();
|
||||||
|
|||||||
Reference in New Issue
Block a user