From 2bdd9acd9f0a01e5363c30dc2704673e362a700a Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Sun, 16 Jun 2024 00:04:42 +0400 Subject: [PATCH 1/5] feat(frontend): add Pipeline middleware that groups all Frontend middlewares --- src/Application.php | 17 +++------ src/Sender/Frontend/Http/Pipeline.php | 53 +++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 src/Sender/Frontend/Http/Pipeline.php diff --git a/src/Application.php b/src/Application.php index a31e5cb7..066d1839 100644 --- a/src/Application.php +++ b/src/Application.php @@ -71,7 +71,7 @@ public function __construct( ]); $this->processors[] = $inspector; - $withFrontend and $this->configureFrontend(8000); + $withFrontend and $this->configureFrontend(); $this->configureFileObserver(); foreach ($map as $config) { @@ -175,27 +175,22 @@ public function prepareServerFiber(SocketServer $config, Inspector $inspector, L }); } - /** - * @param int<1, 65535> $port - */ - public function configureFrontend(int $port): void + public function configureFrontend(): void { - $this->senders[] = $wsSender = Sender\FrontendSender::create($this->logger); + $this->processors[] = $this->senders[] = $wsSender = Sender\FrontendSender::create($this->logger); + $this->container->set($wsSender->getEventStorage(), Sender\Frontend\EventStorage::class); + // Separated port $inspector = $this->container->make(Inspector::class, [ new Traffic\Dispatcher\Http( [ - new Sender\Frontend\Http\Cors(), - new Sender\Frontend\Http\StaticFiles(), - new Sender\Frontend\Http\EventAssets($this->logger, $wsSender->getEventStorage()), - new Sender\Frontend\Http\Router($this->logger, $wsSender->getEventStorage()), + new Sender\Frontend\Http\Pipeline($this->logger, $wsSender), ], [new Sender\Frontend\Http\RequestHandler($wsSender->getConnectionPool())], silentMode: true, ), ]); $this->processors[] = $inspector; - $this->processors[] = $wsSender; $config = $this->container->get(FrontendConfig::class); $this->prepareServerFiber(new SocketServer(port: $config->port), $inspector, $this->logger); } diff --git a/src/Sender/Frontend/Http/Pipeline.php b/src/Sender/Frontend/Http/Pipeline.php new file mode 100644 index 00000000..70e4f036 --- /dev/null +++ b/src/Sender/Frontend/Http/Pipeline.php @@ -0,0 +1,53 @@ + */ + private MiddlewaresPipeline $pipeline; + + public function __construct( + Logger $logger, + \Buggregator\Trap\Sender\FrontendSender $wsSender, + ) { + // Build pipeline of handlers. + $this->pipeline = MiddlewaresPipeline::build( + [ + new Cors(), + new StaticFiles(), + new EventAssets($logger, $wsSender->getEventStorage()), + new Router($logger, $wsSender->getEventStorage()), + ], + /** @see Middleware::handle() */ + 'handle', + static fn(): ResponseInterface => new Response(404), + ResponseInterface::class, + ); + } + + public function handle(ServerRequestInterface $request, callable $next): ResponseInterface + { + /** @var ResponseInterface $response */ + $response = ($this->pipeline)($request); + + return $response->getStatusCode() === 404 + ? $next($request) + : $response; + } +} From d00fce36aa663f95d038b7a14811edcc68e893fe Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Sun, 16 Jun 2024 01:06:31 +0400 Subject: [PATCH 2/5] feat(frontend): frontend may be used on debug port --- src/Application.php | 52 ++++++++++++++++++++++----- src/Command/Run.php | 18 ++++------ src/Handler/Http/Handler/Fallback.php | 5 +++ src/Sender/Frontend/Http/Pipeline.php | 5 ++- src/Service/Container.php | 2 +- 5 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/Application.php b/src/Application.php index 066d1839..d84fa83d 100644 --- a/src/Application.php +++ b/src/Application.php @@ -53,7 +53,16 @@ public function __construct( $this->buffer = new Buffer(bufferSize: 10485760, timer: 0.1); $this->container->set($this->buffer); - $inspector = $container->make(Inspector::class, [ + // Frontend + $feConfig = $this->container->get(FrontendConfig::class); + $feSeparated = $withFrontend && !\in_array( + $feConfig->port, + \array_map(static fn(SocketServer $item): ?int => $item->type === 'tcp' ? $item->port : null, $map, ), + true, + ); + $withFrontend and $this->configureFrontend($feSeparated); + + $inspectorWithFrontend = $inspector = $container->make(Inspector::class, [ // new Traffic\Dispatcher\WebSocket(), new Traffic\Dispatcher\VarDumper(), new Traffic\Dispatcher\Http( @@ -71,11 +80,32 @@ public function __construct( ]); $this->processors[] = $inspector; - $withFrontend and $this->configureFrontend(); + if (!$feSeparated) { + $inspectorWithFrontend = $container->make(Inspector::class, [ + new Traffic\Dispatcher\VarDumper(), + new Traffic\Dispatcher\Http( + [ + $this->container->get(Sender\Frontend\Http\Pipeline::class), + $this->container->get(Middleware\Resources::class), + $this->container->get(Middleware\DebugPage::class), + $this->container->get(Middleware\RayRequestDump::class), + $this->container->get(Middleware\SentryTrap::class), + $this->container->get(Middleware\XHProfTrap::class), + ], + [$this->container->get(Sender\Frontend\Http\RequestHandler::class)], + ), + new Traffic\Dispatcher\Smtp(), + new Traffic\Dispatcher\Monolog(), + ]); + $this->processors[] = $inspectorWithFrontend; + } + $this->configureFileObserver(); foreach ($map as $config) { - $this->prepareServerFiber($config, $inspector, $this->logger); + !$feSeparated && $config->type === 'tcp' && $config->port === $feConfig->port + ? $this->prepareServerFiber($config, $inspectorWithFrontend, $this->logger) + : $this->prepareServerFiber($config, $inspector, $this->logger); } } @@ -175,18 +205,22 @@ public function prepareServerFiber(SocketServer $config, Inspector $inspector, L }); } - public function configureFrontend(): void + public function configureFrontend(bool $separated): void { $this->processors[] = $this->senders[] = $wsSender = Sender\FrontendSender::create($this->logger); - $this->container->set($wsSender->getEventStorage(), Sender\Frontend\EventStorage::class); + $this->container->set($wsSender); + $this->container->set($wsSender->getEventStorage()); + $this->container->set($wsSender->getConnectionPool()); + + if (!$separated) { + return; + } // Separated port $inspector = $this->container->make(Inspector::class, [ new Traffic\Dispatcher\Http( - [ - new Sender\Frontend\Http\Pipeline($this->logger, $wsSender), - ], - [new Sender\Frontend\Http\RequestHandler($wsSender->getConnectionPool())], + [$this->container->get(Sender\Frontend\Http\Pipeline::class)], + [$this->container->get(Sender\Frontend\Http\RequestHandler::class)], silentMode: true, ), ]); diff --git a/src/Command/Run.php b/src/Command/Run.php index fa72e9cf..6e18b28c 100644 --- a/src/Command/Run.php +++ b/src/Command/Run.php @@ -32,6 +32,8 @@ final class Run extends Command implements SignalableCommandInterface { private ?Application $app = null; + private Logger $logger; + private bool $cancelled = false; public function configure(): void @@ -129,6 +131,7 @@ protected function execute( InputInterface $input, OutputInterface $output, ): int { + $this->logger = new Logger($output); try { // Print intro $output->writeln(\sprintf('%s v%s', Info::NAME, Info::version())); @@ -147,7 +150,7 @@ protected function execute( )->finish(); $container->set($registry); $container->set($input, InputInterface::class); - $container->set(new Logger($output)); + $container->set($this->logger); $this->app = $container->get(Application::class, [ 'map' => $this->getServers($container), 'senders' => $registry->getSenders($senders), @@ -157,16 +160,9 @@ protected function execute( $this->app->run(); } catch (\Throwable $e) { - if ($output->isVerbose()) { - // Write colorful exception (title, message, stacktrace) - $output->writeln(\sprintf("%s", $e::class)); - } - - $output->writeln(\sprintf("%s", $e->getMessage())); - - if ($output->isDebug()) { - $output->writeln(\sprintf("%s", $e->getTraceAsString())); - } + do { + $this->logger->exception($e); + } while ($e = $e->getPrevious()); } return Command::SUCCESS; diff --git a/src/Handler/Http/Handler/Fallback.php b/src/Handler/Http/Handler/Fallback.php index 2423ac59..0bea1afb 100644 --- a/src/Handler/Http/Handler/Fallback.php +++ b/src/Handler/Http/Handler/Fallback.php @@ -9,6 +9,7 @@ use Buggregator\Trap\Handler\Http\RequestHandler; use Buggregator\Trap\Handler\Pipeline; use Buggregator\Trap\Proto\Frame; +use Buggregator\Trap\Sender\Frontend\Http\Pipeline as FrontendPipeline; use Buggregator\Trap\Traffic\StreamClient; use Nyholm\Psr7\Response; use Psr\Http\Message\ResponseInterface; @@ -81,6 +82,10 @@ public function handle(StreamClient $streamClient, ServerRequestInterface $reque $streamClient->disconnect(); } + if (isset($response) && $response->hasHeader(FrontendPipeline::FRONTEND_HEADER)) { + return; + } + if (!$gotFrame) { yield new Frame\Http( $request, diff --git a/src/Sender/Frontend/Http/Pipeline.php b/src/Sender/Frontend/Http/Pipeline.php index 70e4f036..b714a429 100644 --- a/src/Sender/Frontend/Http/Pipeline.php +++ b/src/Sender/Frontend/Http/Pipeline.php @@ -6,6 +6,7 @@ use Buggregator\Trap\Handler\Http\Middleware; use Buggregator\Trap\Handler\Pipeline as MiddlewaresPipeline; +use Buggregator\Trap\Info; use Buggregator\Trap\Logger; use Nyholm\Psr7\Response; use Psr\Http\Message\ResponseInterface; @@ -19,6 +20,8 @@ */ final class Pipeline implements Middleware { + public const FRONTEND_HEADER = 'X-Trap-Frontend'; + /** @var MiddlewaresPipeline */ private MiddlewaresPipeline $pipeline; @@ -48,6 +51,6 @@ public function handle(ServerRequestInterface $request, callable $next): Respons return $response->getStatusCode() === 404 ? $next($request) - : $response; + : $response->withHeader(self::FRONTEND_HEADER, Info::version()); } } diff --git a/src/Service/Container.php b/src/Service/Container.php index 32acfafb..76fcd90f 100644 --- a/src/Service/Container.php +++ b/src/Service/Container.php @@ -88,7 +88,7 @@ public function make(string $class, array $arguments = []): object try { $result = $this->injector->make($class, \array_merge((array) $binding, $arguments)); } catch (\Throwable $e) { - throw new class("Unable to create object of class $class.", previous: $e, ) extends \RuntimeException implements NotFoundExceptionInterface {}; + throw new class("Unable to create object of class $class.", previous: $e) extends \RuntimeException implements NotFoundExceptionInterface {}; } } From b94a4723e3520f209b799907ac89a5337fad9358 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Sun, 16 Jun 2024 01:14:36 +0400 Subject: [PATCH 3/5] feat(frontend): set debug ports to `1025`, `8000`, `9912`, `9913` by default --- src/Application.php | 7 ++++--- src/Command/Run.php | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Application.php b/src/Application.php index d84fa83d..92a9dc92 100644 --- a/src/Application.php +++ b/src/Application.php @@ -55,7 +55,7 @@ public function __construct( // Frontend $feConfig = $this->container->get(FrontendConfig::class); - $feSeparated = $withFrontend && !\in_array( + $feSeparated = !\in_array( $feConfig->port, \array_map(static fn(SocketServer $item): ?int => $item->type === 'tcp' ? $item->port : null, $map, ), true, @@ -80,7 +80,8 @@ public function __construct( ]); $this->processors[] = $inspector; - if (!$feSeparated) { + + if ($withFrontend && !$feSeparated) { $inspectorWithFrontend = $container->make(Inspector::class, [ new Traffic\Dispatcher\VarDumper(), new Traffic\Dispatcher\Http( @@ -103,7 +104,7 @@ public function __construct( $this->configureFileObserver(); foreach ($map as $config) { - !$feSeparated && $config->type === 'tcp' && $config->port === $feConfig->port + $withFrontend && !$feSeparated && $config->type === 'tcp' && $config->port === $feConfig->port ? $this->prepareServerFiber($config, $inspectorWithFrontend, $this->logger) : $this->prepareServerFiber($config, $inspector, $this->logger); } diff --git a/src/Command/Run.php b/src/Command/Run.php index 6e18b28c..91bb1adb 100644 --- a/src/Command/Run.php +++ b/src/Command/Run.php @@ -63,7 +63,7 @@ public function getServers(Container $container): array $config = $container->get(TcpPorts::class); $servers = []; - $ports = $config->ports ?: [9912]; + $ports = $config->ports ?: [1025, 8000, 9912, 9913]; /** @var scalar $port */ foreach ($ports as $port) { \is_numeric($port) or throw new \InvalidArgumentException( From 7b27b075a9f89b93fddf688cd1b3d420e4949410 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 17 Jun 2024 00:43:30 +0400 Subject: [PATCH 4/5] docs: update information about default ports --- README.md | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e6570a09..28acb737 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ And that's it. Trap is [ready to go](#usage). Buggregator Trap provides a toolkit for use in your code. Firstly, just having Buggregator Trap in your package enhances the capabilities of Symfony Var-Dumper. -If you've already worked with google/protobuf, you probably know how unpleasant it can be. +If you've already worked with `google/protobuf`, you probably know how unpleasant it can be. Now let's compare the dumps of protobuf-message: on the left (with trap) and on the right (without trap). ![trap-proto-diff](https://github.com/buggregator/trap/assets/4152481/30662429-809e-422a-83c6-61d7d2788b18) @@ -171,18 +171,11 @@ function handle($input) { Trap automatically recognizes the type of traffic. Therefore, there is no need to open separate ports for different protocols. -By default, it operates on port `9912`. +By default, it operates on the same ports as the Buggregator Server: `9912`, `9913`, `1025`, and `8000`. However, if you wish to utilize a different port, you can easily make this adjustment using the `-p` option: ```bash -vendor/bin/trap -p8000 -``` - -Sometimes, it's convenient to run Trap on the same ports that [Buggregator](https://github.com/buggregator/server) -uses by default. Well, that's also possible: - -```bash -vendor/bin/trap -p1025 -p9912 -p9913 -p8000 --ui=8080 +vendor/bin/trap -p9912 --ui=8000 ``` Environment variables can also be used to set endpoints: From 35dc4cf27bf212542cdc3b1c6c2d3e5182521365 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 17 Jun 2024 00:53:50 +0400 Subject: [PATCH 5/5] chore: fix psalm issues --- psalm.xml | 5 +++++ src/Handler/Http/Handler/Fallback.php | 4 +++- src/Sender/Frontend/Http/Pipeline.php | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/psalm.xml b/psalm.xml index a6e9f8b6..17dc32fc 100644 --- a/psalm.xml +++ b/psalm.xml @@ -10,6 +10,11 @@ + + + + + diff --git a/src/Handler/Http/Handler/Fallback.php b/src/Handler/Http/Handler/Fallback.php index 0bea1afb..0c1d5fd9 100644 --- a/src/Handler/Http/Handler/Fallback.php +++ b/src/Handler/Http/Handler/Fallback.php @@ -82,7 +82,9 @@ public function handle(StreamClient $streamClient, ServerRequestInterface $reque $streamClient->disconnect(); } - if (isset($response) && $response->hasHeader(FrontendPipeline::FRONTEND_HEADER)) { + /** @var mixed $response */ + $response ??= null; + if ($response instanceof ResponseInterface && $response->hasHeader(FrontendPipeline::FRONTEND_HEADER)) { return; } diff --git a/src/Sender/Frontend/Http/Pipeline.php b/src/Sender/Frontend/Http/Pipeline.php index b714a429..0b8eb30b 100644 --- a/src/Sender/Frontend/Http/Pipeline.php +++ b/src/Sender/Frontend/Http/Pipeline.php @@ -30,6 +30,7 @@ public function __construct( \Buggregator\Trap\Sender\FrontendSender $wsSender, ) { // Build pipeline of handlers. + /** @var MiddlewaresPipeline */ $this->pipeline = MiddlewaresPipeline::build( [ new Cors(),