Skip to content

Commit

Permalink
Add mapper for HTTP dumps
Browse files Browse the repository at this point in the history
  • Loading branch information
roxblnfk committed Dec 15, 2023
1 parent 6aead78 commit 5047486
Show file tree
Hide file tree
Showing 16 changed files with 219 additions and 59 deletions.
2 changes: 1 addition & 1 deletion src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ public function configureFrontend(int $port): void
new Traffic\Dispatcher\Http(
[
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\Version(),
],
[new Sender\Frontend\Http\RequestHandler($wsSender->getConnectionPool())],

Check failure on line 181 in src/Application.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.2, OS ubuntu-latest)

InternalClass

src/Application.php:181:18: InternalClass: Buggregator\Trap\Sender\Frontend\Http\RequestHandler is internal to Buggregator\Trap * Read about Sec-WebSocket-Extensions: but called from Buggregator\Trap\Application (see https://psalm.dev/174)

Check failure on line 181 in src/Application.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.2, OS ubuntu-latest)

InternalMethod

src/Application.php:181:18: InternalMethod: Constructor Buggregator\Trap\Sender\Frontend\Http\RequestHandler::__construct is internal to Buggregator\Trap * Read about Sec-WebSocket-Extensions: but called from Buggregator\Trap\Application::configureFrontend (see https://psalm.dev/175)
silentMode: true,
Expand Down
33 changes: 28 additions & 5 deletions src/Proto/Frame/Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,25 @@
use Nyholm\Psr7\ServerRequest;
use Nyholm\Psr7\UploadedFile;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UploadedFileInterface;

/**
* @internal
* @psalm-internal Buggregator
*/
final class Http extends Frame implements FilesCarrier
{
private readonly int $cachedSize;

public function __construct(
public readonly ServerRequestInterface $request,
DateTimeImmutable $time = new DateTimeImmutable(),
) {
$this->cachedSize = $request->getBody()->getSize() + \array_reduce(

Check failure on line 29 in src/Proto/Frame/Http.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.2, OS ubuntu-latest)

PossiblyNullOperand

src/Proto/Frame/Http.php:29:29: PossiblyNullOperand: Left operand cannot be nullable, got int|null (see https://psalm.dev/080)
\iterator_to_array($this->iterateUploadedFiles(), false),
static fn(int $carry, UploadedFileInterface $file): int => $carry + $file->getSize(),

Check failure on line 31 in src/Proto/Frame/Http.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.2, OS ubuntu-latest)

PossiblyNullOperand

src/Proto/Frame/Http.php:31:85: PossiblyNullOperand: Right operand cannot be nullable, got int|null (see https://psalm.dev/080)
0,
);
parent::__construct(type: ProtoType::HTTP, time: $time);
}

Expand Down Expand Up @@ -78,11 +86,7 @@ public static function fromString(string $payload, DateTimeImmutable $time): sta

public function getSize(): int
{
return $this->request->getBody()->getSize() + \array_reduce(
$this->request->getUploadedFiles(),
static fn(int $carry, array $file): int => $carry + $file['size'],
0
);
return $this->cachedSize;
}

public function hasFiles(): bool
Expand All @@ -94,4 +98,23 @@ public function getFiles(): array
{
return $this->request->getUploadedFiles();
}

/**
* @return \Generator<array-key, UploadedFileInterface, mixed, void>
*/
public function iterateUploadedFiles(): \Generator
{
$generator = static function (array $files) use (&$generator): \Generator {
foreach ($files as $file) {
if (\is_array($file)) {
yield from $generator($file);
continue;
}

yield $file;
}
};

return $generator($this->request->getUploadedFiles());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@

declare(strict_types=1);

namespace Buggregator\Trap\Sender\Frontend\Message;
namespace Buggregator\Trap\Sender\Frontend;

use ArrayAccess;
use Buggregator\Trap\Sender\Frontend\Event\Asset;

final class Event implements \JsonSerializable
{
/**
* @param non-empty-string $uuid
* @param non-empty-string $type
* @param ArrayAccess<non-empty-string, Asset>|null $assets
*/
public function __construct(
public readonly string $uuid,
public readonly string $type,
public readonly array $payload,
public readonly float $timestamp,
public readonly ?string $projectId = null,
public readonly ?ArrayAccess $assets = null,
) {
}

Expand All @@ -26,7 +31,7 @@ public function jsonSerialize(): array
'type' => $this->type,
'payload' => $this->payload,
'timestamp' => $this->timestamp,
'projectId' => $this->projectId,
'project_id' => $this->projectId,
];
}
}
16 changes: 16 additions & 0 deletions src/Sender/Frontend/Event/Asset.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Buggregator\Trap\Sender\Frontend\Event;

/**
* @internal
*/
abstract class Asset
{
public function __construct(
public readonly string $uuid,
) {
}
}
23 changes: 23 additions & 0 deletions src/Sender/Frontend/Event/AttachedFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Buggregator\Trap\Sender\Frontend\Event;

use Psr\Http\Message\UploadedFileInterface;

/**
* @internal
*/
final class AttachedFile extends Asset
{
/**
* @param non-empty-string $id
*/
public function __construct(
string $id,
public readonly UploadedFileInterface $file,
) {
parent::__construct($id);
}
}
1 change: 0 additions & 1 deletion src/Sender/Frontend/EventsStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Buggregator\Trap\Sender\Frontend;

use Buggregator\Trap\Sender\Frontend\Message\Event;
use IteratorAggregate;

/**
Expand Down
11 changes: 9 additions & 2 deletions src/Sender/Frontend/FrameHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Buggregator\Trap\Sender\Frontend;

use Buggregator\Trap\Logger;
use Buggregator\Trap\Proto\Frame;
use Buggregator\Trap\Sender\FrameHandler as HandlerInterface;
use Buggregator\Trap\Sender\Frontend\Message\Push;
Expand All @@ -17,6 +18,7 @@ final class FrameHandler implements HandlerInterface
private readonly FrameMapper $frameMapper;

public function __construct(
private readonly Logger $logger,
private readonly ConnectionPool $connectionPool,
private readonly EventsStorage $eventsStorage,
) {
Expand All @@ -25,8 +27,13 @@ public function __construct(

public function handle(Frame $frame): void
{
$this->eventsStorage->add($event = $this->frameMapper->map($frame));
unset($frame);
try {
$this->eventsStorage->add($event = $this->frameMapper->map($frame));
unset($frame);
} catch (\Throwable $e) {
$this->logger->error('Mapping frame failed: %s', $e->getMessage());
return;
}

// Send event to all connections
$this->connectionPool->send(\Buggregator\Trap\Traffic\Websocket\Frame::text(
Expand Down
2 changes: 1 addition & 1 deletion src/Sender/Frontend/FrameMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Buggregator\Trap\Sender\Frontend;

use Buggregator\Trap\Proto\Frame;
use Buggregator\Trap\Sender\Frontend\Message\Event;
use IteratorAggregate;

/**
Expand All @@ -18,6 +17,7 @@ public function map(Frame $frame): Event
{
return match ($frame::class) {
Frame\VarDumper::class => (new Mapper\VarDump())->map($frame),
Frame\Http::class => (new Mapper\HttpRequest())->map($frame),
default => throw new \InvalidArgumentException('Unknown frame type ' . $frame::class),
};
}
Expand Down
64 changes: 64 additions & 0 deletions src/Sender/Frontend/Http/EventAssets.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace Buggregator\Trap\Sender\Frontend\Http;

use Buggregator\Trap\Handler\Http\Middleware;
use Buggregator\Trap\Logger;
use Buggregator\Trap\Sender\Frontend\Event\AttachedFile;
use Buggregator\Trap\Sender\Frontend\EventsStorage;
use Nyholm\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

/**
* @internal
* @psalm-internal Buggregator\Trap
*/
final class EventAssets implements Middleware
{
public function __construct(
private readonly Logger $logger,
private readonly EventsStorage $eventsStorage,
) {
}

public function handle(ServerRequestInterface $request, callable $next): ResponseInterface
{
$path = $request->getUri()->getPath();
if (\preg_match('#^/api/smtp/([^/]++)/attachment/([^/]++)#', $path, $matches) !== 1) {
return $next($request);
}


// Find event
$event = $this->eventsStorage->get($matches[1]);
if ($event === null) {
$this->logger->debug('Get attachment %s for event %s. Event not found.', $matches[2], $matches[1]);
return new Response(404);
}

// Find attachment
$attachment = $event->assets[$matches[2]] ?? null;

if (!$attachment instanceof AttachedFile) {
$this->logger->debug('Get attachment %s for event %s. Attached file not found.', $matches[2], $matches[1]);
return new Response(404);
}

return new Response(
200,
[
'Content-Type' => $attachment->file->getClientMediaType(),
'Content-Disposition' => \sprintf(
"attachment; filename=\"%s\"",
\rawurlencode($attachment->file->getClientFilename()),
),
'Content-Length' => (string)$attachment->file->getSize(),
'Cache-Control' => 'no-cache',
],
$attachment->file->getStream(),
);
}
}
1 change: 0 additions & 1 deletion src/Sender/Frontend/Http/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ public function handle(ServerRequestInterface $request, callable $next): Respons

$handler = $this->router->match(Method::fromString($method), $path);

\trap($path, $method, $handler)->if(Method::fromString($method) === Method::Delete);
if ($handler === null) {
return new Response(404);
}
Expand Down
40 changes: 0 additions & 40 deletions src/Sender/Frontend/Http/Version.php

This file was deleted.

64 changes: 64 additions & 0 deletions src/Sender/Frontend/Mapper/HttpRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace Buggregator\Trap\Sender\Frontend\Mapper;

use ArrayObject;
use Buggregator\Trap\Proto\Frame\Http as HttpFrame;
use Buggregator\Trap\Sender\Frontend\Event;
use Buggregator\Trap\Support\Uuid;
use Psr\Http\Message\UploadedFileInterface;

/**
* @internal
*/
final class HttpRequest
{
public function map(HttpFrame $frame): Event
{
$request = $frame->request;

$uri = \ltrim($request->getUri()->getPath(), '/');
$assets = new ArrayObject();

return new Event(
uuid: $uuid = Uuid::uuid4(),
type: 'http-dump',
payload: [
'received_at' => $frame->time->format('Y-m-d H:i:s'),
'host' => $request->getHeaderLine('Host'),
'request' => [
'method' => $request->getMethod(),
'uri' => $uri,
'headers' => $request->getHeaders(),
'body' => (string)$request->getBody(),
'query' => $request->getQueryParams(),
'post' => $request->getParsedBody() ?? [],
'cookies' => $request->getCookieParams(),
'files' => \array_map(
static function (UploadedFileInterface $attachment) use ($assets, $uuid): array {
$asset = new Event\AttachedFile(
id: Uuid::uuid4(),
file: $attachment,
);
$uri = $uuid . '/' . $asset->uuid;
$assets->offsetSet($asset->uuid, $asset);

return [
'id' => $asset->uuid,
'name' => $attachment->getClientFilename(),
'uri' => $uri,
'size' => $attachment->getSize(),
'mime' => $attachment->getClientMediaType(),
];
},
\iterator_to_array($frame->iterateUploadedFiles(), false),
),
],
],
timestamp: (float)$frame->time->format('U.u'),
assets: $assets,
);
}
}
2 changes: 1 addition & 1 deletion src/Sender/Frontend/Mapper/VarDump.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Buggregator\Trap\Sender\Frontend\Mapper;

use Buggregator\Trap\Proto\Frame\VarDumper;
use Buggregator\Trap\Sender\Frontend\Message\Event;
use Buggregator\Trap\Sender\Frontend\Event;
use Buggregator\Trap\Support\Uuid;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\Stub;
Expand Down
Loading

0 comments on commit 5047486

Please sign in to comment.