mirror of
https://github.com/rekryt/iplist.git
synced 2025-10-13 00:49:36 +03:00
Initial commit
This commit is contained in:
107
src/Infrastructure/API/App.php
Normal file
107
src/Infrastructure/API/App.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Infrastructure\API;
|
||||
|
||||
use Amp\ByteStream\WritableResourceStream;
|
||||
use Amp\Log\ConsoleFormatter;
|
||||
use Amp\Log\StreamHandler;
|
||||
|
||||
use Revolt\EventLoop;
|
||||
use Revolt\EventLoop\UnsupportedFeatureException;
|
||||
|
||||
use Closure;
|
||||
use Dotenv\Dotenv;
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
use function Amp\trapSignal;
|
||||
use function OpenCCK\getEnv;
|
||||
use function sprintf;
|
||||
|
||||
final class App {
|
||||
private static App $_instance;
|
||||
|
||||
/**
|
||||
* @param array<AppModuleInterface> $modules
|
||||
*/
|
||||
private array $modules = [];
|
||||
|
||||
private bool $isEventLoopStarted = false;
|
||||
|
||||
/**
|
||||
* @param ?Logger $logger
|
||||
*/
|
||||
private function __construct(private ?Logger $logger = null) {
|
||||
ini_set('memory_limit', getEnv('SYS_MEMORY_LIMIT') ?? '2048M');
|
||||
|
||||
if (!defined('PATH_ROOT')) {
|
||||
define('PATH_ROOT', dirname(__DIR__, 3));
|
||||
}
|
||||
|
||||
$dotenv = Dotenv::createImmutable(PATH_ROOT);
|
||||
$dotenv->safeLoad();
|
||||
|
||||
if ($timezone = getEnv('SYS_TIMEZONE')) {
|
||||
date_default_timezone_set($timezone);
|
||||
}
|
||||
|
||||
$this->logger = $logger ?? new Logger(getEnv('COMPOSE_PROJECT_NAME') ?? 'iplist');
|
||||
$logHandler = new StreamHandler(new WritableResourceStream(STDOUT));
|
||||
$logHandler->setFormatter(new ConsoleFormatter());
|
||||
$logHandler->setLevel(getEnv('DEBUG') === 'false' ? LogLevel::INFO : LogLevel::DEBUG);
|
||||
$this->logger->pushHandler($logHandler);
|
||||
|
||||
EventLoop::setErrorHandler(function ($e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
public static function getInstance(?Logger $logger = null): self {
|
||||
return self::$_instance ??= new self($logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Closure<AppModuleInterface> $handler
|
||||
* @return $this
|
||||
*/
|
||||
public function addModule(Closure $handler): self {
|
||||
$module = $handler($this);
|
||||
$this->modules[$module::class] = $module;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getModules(): array {
|
||||
return $this->modules;
|
||||
}
|
||||
|
||||
public static function getLogger(): ?Logger {
|
||||
return self::$_instance->logger;
|
||||
}
|
||||
|
||||
public function start(): void {
|
||||
foreach ($this->getModules() as $module) {
|
||||
$module->start();
|
||||
}
|
||||
if (defined('SIGINT') && defined('SIGTERM')) {
|
||||
// Await SIGINT or SIGTERM to be received.
|
||||
try {
|
||||
$signal = trapSignal([SIGINT, SIGTERM]);
|
||||
$this->logger->info(sprintf('Received signal %d, stopping server', $signal));
|
||||
} catch (UnsupportedFeatureException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
}
|
||||
$this->stop();
|
||||
} else {
|
||||
if (!$this->isEventLoopStarted && !defined('PHPUNIT_COMPOSER_INSTALL')) {
|
||||
$this->isEventLoopStarted = true;
|
||||
EventLoop::run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function stop(): void {
|
||||
foreach ($this->modules as $module) {
|
||||
$module->stop();
|
||||
}
|
||||
}
|
||||
}
|
8
src/Infrastructure/API/AppModuleInterface.php
Normal file
8
src/Infrastructure/API/AppModuleInterface.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Infrastructure\API;
|
||||
|
||||
interface AppModuleInterface {
|
||||
public function start(): void;
|
||||
public function stop(): void;
|
||||
}
|
17
src/Infrastructure/API/Handler/HTTPErrorHandler.php
Normal file
17
src/Infrastructure/API/Handler/HTTPErrorHandler.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Infrastructure\API\Handler;
|
||||
|
||||
use Amp\Http\Server\ErrorHandler;
|
||||
use Amp\Http\Server\Request;
|
||||
use Amp\Http\Server\Response;
|
||||
|
||||
final class HTTPErrorHandler implements ErrorHandler {
|
||||
public function handleError(int $status, ?string $reason = null, ?Request $request = null): Response {
|
||||
return new Response(
|
||||
status: $status,
|
||||
headers: ['content-type' => 'application/json; charset=utf-8'],
|
||||
body: json_encode(['message' => $reason, 'code' => $status])
|
||||
);
|
||||
}
|
||||
}
|
58
src/Infrastructure/API/Handler/HTTPHandler.php
Normal file
58
src/Infrastructure/API/Handler/HTTPHandler.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Infrastructure\API\Handler;
|
||||
|
||||
use Amp\Http\Server\Request;
|
||||
use Amp\Http\Server\RequestHandler;
|
||||
use Amp\Http\Server\RequestHandler\ClosureRequestHandler;
|
||||
use Amp\Http\Server\Response;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
|
||||
use function OpenCCK\getEnv;
|
||||
|
||||
final class HTTPHandler extends Handler implements HTTPHandlerInterface {
|
||||
private function __construct(private readonly LoggerInterface $logger, array $headers = null) {
|
||||
}
|
||||
|
||||
public static function getInstance(LoggerInterface $logger, array $headers = null): HTTPHandler {
|
||||
return new self($logger, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $controllerName
|
||||
* @return RequestHandler
|
||||
*/
|
||||
public function getHandler(string $controllerName = 'main'): RequestHandler {
|
||||
return new ClosureRequestHandler(function (Request $request) use ($controllerName): Response {
|
||||
try {
|
||||
$response = $this->getController(
|
||||
ucfirst($request->getQueryParameter('format') ?: $controllerName),
|
||||
$request,
|
||||
$this->headers ?? []
|
||||
)();
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->warning('Exception', [
|
||||
'exception' => $e::class,
|
||||
'error' => $e->getMessage(),
|
||||
'code' => $e->getCode(),
|
||||
]);
|
||||
$response = new Response(
|
||||
status: $e->getCode() ?: 500,
|
||||
headers: $this->headers ?? ['content-type' => 'application/json; charset=utf-8'],
|
||||
body: json_encode(
|
||||
array_merge(
|
||||
['message' => $e->getMessage(), 'code' => $e->getCode()],
|
||||
getEnv('DEBUG') === 'true'
|
||||
? ['file' => $e->getFile() . ':' . $e->getLine(), 'trace' => $e->getTrace()]
|
||||
: []
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
}
|
12
src/Infrastructure/API/Handler/HTTPHandlerInterface.php
Normal file
12
src/Infrastructure/API/Handler/HTTPHandlerInterface.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Infrastructure\API\Handler;
|
||||
|
||||
use Amp\Http\Server\RequestHandler;
|
||||
|
||||
interface HTTPHandlerInterface extends HandlerInterface {
|
||||
/**
|
||||
* @return RequestHandler
|
||||
*/
|
||||
public function getHandler(): RequestHandler;
|
||||
}
|
27
src/Infrastructure/API/Handler/Handler.php
Normal file
27
src/Infrastructure/API/Handler/Handler.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Infrastructure\API\Handler;
|
||||
|
||||
use Amp\Http\HttpStatus;
|
||||
use Amp\Http\Server\Request;
|
||||
|
||||
use OpenCCK\App\Controller\AbstractController;
|
||||
|
||||
use Exception;
|
||||
|
||||
abstract class Handler implements HandlerInterface {
|
||||
/**
|
||||
* @param string $name
|
||||
* @param Request $request
|
||||
* @param ?string[] $headers
|
||||
* @return AbstractController
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function getController(string $name, Request $request, array $headers = null): AbstractController {
|
||||
$className = '\\OpenCCK\\App\\Controller\\' . ucfirst($name) . 'Controller';
|
||||
if (!class_exists($className)) {
|
||||
throw new Exception('Controller ' . $className . ' not found', HttpStatus::NOT_FOUND);
|
||||
}
|
||||
return new $className($request, $headers ?? ['content-type' => 'text/plain']);
|
||||
}
|
||||
}
|
12
src/Infrastructure/API/Handler/HandlerInterface.php
Normal file
12
src/Infrastructure/API/Handler/HandlerInterface.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Infrastructure\API\Handler;
|
||||
|
||||
use Amp\Http\Server\RequestHandler;
|
||||
|
||||
interface HandlerInterface {
|
||||
/**
|
||||
* @return RequestHandler
|
||||
*/
|
||||
public function getHandler(): RequestHandler;
|
||||
}
|
119
src/Infrastructure/API/Server.php
Normal file
119
src/Infrastructure/API/Server.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Infrastructure\API;
|
||||
|
||||
use Amp\ByteStream\BufferException;
|
||||
use Amp\Http\Server\DefaultErrorHandler;
|
||||
use Amp\Http\Server\Driver\ConnectionLimitingClientFactory;
|
||||
use Amp\Http\Server\Driver\ConnectionLimitingServerSocketFactory;
|
||||
use Amp\Http\Server\Driver\DefaultHttpDriverFactory;
|
||||
use Amp\Http\Server\Driver\SocketClientFactory;
|
||||
use Amp\Http\Server\ErrorHandler;
|
||||
use Amp\Http\Server\HttpServer;
|
||||
use Amp\Http\Server\HttpServerStatus;
|
||||
use Amp\Http\Server\SocketHttpServer;
|
||||
use Amp\Socket\BindContext;
|
||||
use Amp\Sync\LocalSemaphore;
|
||||
use Amp\Socket;
|
||||
|
||||
use Monolog\Logger;
|
||||
use OpenCCK\App\Service\IPListService;
|
||||
use OpenCCK\Infrastructure\API\Handler\HTTPHandler;
|
||||
use Throwable;
|
||||
|
||||
use function OpenCCK\getEnv;
|
||||
|
||||
final class Server implements AppModuleInterface {
|
||||
private static Server $_instance;
|
||||
|
||||
private int $connectionLimit = 1024;
|
||||
private int $connectionPerIpLimit = 10;
|
||||
|
||||
/**
|
||||
* @param ?HttpServer $httpServer
|
||||
* @param ?ErrorHandler $errorHandler
|
||||
* @param ?BindContext $bindContext
|
||||
* @param ?Logger $logger
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function __construct(
|
||||
private ?HttpServer $httpServer,
|
||||
private ?ErrorHandler $errorHandler,
|
||||
private ?Socket\BindContext $bindContext,
|
||||
private ?Logger $logger
|
||||
) {
|
||||
$this->logger = $logger ?? App::getLogger();
|
||||
$serverSocketFactory = new ConnectionLimitingServerSocketFactory(new LocalSemaphore($this->connectionLimit));
|
||||
$clientFactory = new ConnectionLimitingClientFactory(
|
||||
new SocketClientFactory($this->logger),
|
||||
$this->logger,
|
||||
$this->connectionPerIpLimit
|
||||
);
|
||||
$this->httpServer =
|
||||
$httpServer ??
|
||||
new SocketHttpServer(
|
||||
logger: $this->logger,
|
||||
serverSocketFactory: $serverSocketFactory,
|
||||
clientFactory: $clientFactory,
|
||||
httpDriverFactory: new DefaultHttpDriverFactory(logger: $this->logger, streamTimeout: 60)
|
||||
);
|
||||
$this->bindContext = $bindContext ?? (new Socket\BindContext())->withoutTlsContext();
|
||||
$this->errorHandler = $errorHandler ?? new DefaultErrorHandler();
|
||||
|
||||
// инициализация сервиса
|
||||
IPListService::getInstance($this->logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?HttpServer $httpServer
|
||||
* @param ?ErrorHandler $errorHandler
|
||||
* @param ?BindContext $bindContext
|
||||
* @param ?Logger $logger
|
||||
* @throws BufferException
|
||||
* @throws Throwable
|
||||
*/
|
||||
public static function getInstance(
|
||||
HttpServer $httpServer = null,
|
||||
ErrorHandler $errorHandler = null,
|
||||
Socket\BindContext $bindContext = null,
|
||||
Logger $logger = null
|
||||
): Server {
|
||||
return self::$_instance ??= new self($httpServer, $errorHandler, $bindContext, $logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Запуск веб-сервера
|
||||
* @return void
|
||||
*/
|
||||
public function start(): void {
|
||||
try {
|
||||
$this->httpServer->expose(
|
||||
new Socket\InternetAddress(getEnv('HTTP_HOST') ?? '0.0.0.0', getEnv('HTTP_PORT') ?? 8080),
|
||||
$this->bindContext
|
||||
);
|
||||
//$this->socketHttpServer->expose(
|
||||
// new Socket\InternetAddress('[::]', $_ENV['HTTP_PORT'] ?? 8080),
|
||||
// $this->bindContext
|
||||
//);
|
||||
$this->httpServer->start(HTTPHandler::getInstance($this->logger)->getHandler(), $this->errorHandler);
|
||||
} catch (Socket\SocketException $e) {
|
||||
$this->logger->warning($e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function stop(): void {
|
||||
$this->httpServer->stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HttpServerStatus
|
||||
*/
|
||||
public function getStatus(): HttpServerStatus {
|
||||
return $this->httpServer->getStatus();
|
||||
}
|
||||
}
|
44
src/Infrastructure/Storage/CIDRStorage.php
Normal file
44
src/Infrastructure/Storage/CIDRStorage.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Infrastructure\Storage;
|
||||
|
||||
use OpenCCK\Infrastructure\API\App;
|
||||
use Revolt\EventLoop;
|
||||
|
||||
class CIDRStorage implements StorageInterface {
|
||||
const FILENAME = 'cidr.json';
|
||||
|
||||
private static CIDRStorage $_instance;
|
||||
private array $data = [];
|
||||
|
||||
private function __construct() {
|
||||
$path = PATH_ROOT . '/storage/' . self::FILENAME;
|
||||
if (is_file($path)) {
|
||||
$this->data = (array) json_decode(file_get_contents($path)) ?? [];
|
||||
}
|
||||
|
||||
EventLoop::repeat(\OpenCCK\getEnv('STORAGE_SAVE_INTERVAL') ?? 120, $this->save(...));
|
||||
}
|
||||
|
||||
public static function getInstance(): CIDRStorage {
|
||||
return self::$_instance ??= new self();
|
||||
}
|
||||
|
||||
public function get(string $key): ?array {
|
||||
return $this->data[$key] ?? null;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value): bool {
|
||||
$this->data[$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function has(string $key): bool {
|
||||
return isset($this->data[$key]);
|
||||
}
|
||||
|
||||
private function save(): void {
|
||||
file_put_contents(PATH_ROOT . '/storage/' . self::FILENAME, json_encode($this->data, JSON_PRETTY_PRINT));
|
||||
App::getLogger()->notice('Whois storage saved', [count($this->data) . ' items']);
|
||||
}
|
||||
}
|
9
src/Infrastructure/Storage/StorageInterface.php
Normal file
9
src/Infrastructure/Storage/StorageInterface.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Infrastructure\Storage;
|
||||
|
||||
interface StorageInterface {
|
||||
public function get(string $key): mixed;
|
||||
public function set(string $key, mixed $value): bool;
|
||||
public function has(string $key): bool;
|
||||
}
|
20
src/Infrastructure/Task/ShellTask.php
Normal file
20
src/Infrastructure/Task/ShellTask.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Infrastructure\Task;
|
||||
|
||||
use Amp\Cancellation;
|
||||
use Amp\Sync\Channel;
|
||||
|
||||
readonly class ShellTask implements TaskInterface {
|
||||
public function __construct(private string $command) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Channel $channel
|
||||
* @param Cancellation $cancellation
|
||||
* @return string
|
||||
*/
|
||||
public function run(Channel $channel, Cancellation $cancellation): mixed {
|
||||
return shell_exec($this->command);
|
||||
}
|
||||
}
|
11
src/Infrastructure/Task/TaskInterface.php
Normal file
11
src/Infrastructure/Task/TaskInterface.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Infrastructure\Task;
|
||||
|
||||
use Amp\Cancellation;
|
||||
use Amp\Parallel\Worker\Task;
|
||||
use Amp\Sync\Channel;
|
||||
|
||||
interface TaskInterface extends Task {
|
||||
public function run(Channel $channel, Cancellation $cancellation): mixed;
|
||||
}
|
Reference in New Issue
Block a user