Skip to content

Commit

Permalink
Turn LogHttpArchive into an event listener instead of separate listen…
Browse files Browse the repository at this point in the history
…er and interceptor
  • Loading branch information
kelunik committed Jul 2, 2023
1 parent a2d76c0 commit 23e1958
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 193 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ See [`amphp/http-client-cookies`](https://github.com/amphp/http-client-cookies).

### Logging

The `LogHttpArchive` interceptor allows logging all requests / responses including detailed timing information to an [HTTP archive (HAR)](https://en.wikipedia.org/wiki/HAR_%28file_format%29).
The `LogHttpArchive` event listener allows logging all requests / responses including detailed timing information to an [HTTP archive (HAR)](https://en.wikipedia.org/wiki/HAR_%28file_format%29).

These log files can then be imported into the browsers developer tools or online tools like [HTTP Archive Viewer](http://www.softwareishard.com/har/viewer/) or [Google's HAR Analyzer](https://toolbox.googleapps.com/apps/har_analyzer/).

Expand All @@ -271,10 +271,10 @@ These log files can then be imported into the browsers developer tools or online
```php
use Amp\Http\Client\HttpClientBuilder;
use Amp\Http\Client\Interceptor\LogHttpArchive;
use Amp\Http\Client\EventListener\LogHttpArchive;

$httpClient = (new HttpClientBuilder)
->intercept(new LogHttpArchive('/tmp/http-client.har'))
->listen(new LogHttpArchive('/tmp/http-client.har'))
->build();

$httpClient->request(...);
Expand Down
4 changes: 2 additions & 2 deletions examples/basic/6-customization.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php declare(strict_types=1);

use Amp\Http\Client\EventListener\LogHttpArchive;
use Amp\Http\Client\HttpClientBuilder;
use Amp\Http\Client\HttpException;
use Amp\Http\Client\Interceptor\LogHttpArchive;
use Amp\Http\Client\Interceptor\MatchOrigin;
use Amp\Http\Client\Interceptor\SetRequestHeader;
use Amp\Http\Client\Request;
Expand All @@ -11,7 +11,7 @@

try {
$client = (new HttpClientBuilder)
->intercept(new LogHttpArchive(__DIR__ . '/log.har'))
->listen(new LogHttpArchive(__DIR__ . '/log.har'))
->intercept(new MatchOrigin(['https://amphp.org' => new SetRequestHeader('x-amphp', 'true')]))
->followRedirects(0)
->retry(3)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
<?php declare(strict_types=1);
/** @noinspection ALL */

namespace Amp\Http\Client\Interceptor;
namespace Amp\Http\Client\EventListener;

use Amp\Cancellation;
use Amp\File;
use Amp\File\File;
use Amp\File\Filesystem;
use Amp\File\Whence;
use Amp\ForbidCloning;
use Amp\ForbidSerialization;
use Amp\Http\Client\ApplicationInterceptor;
use Amp\Http\Client\DelegateHttpClient;
use Amp\Http\Client\Connection\Connection;
use Amp\Http\Client\Connection\Stream;
use Amp\Http\Client\EventListener;
use Amp\Http\Client\EventListener\RecordHarAttributes;
use Amp\Http\Client\HttpException;
use Amp\Http\Client\Internal\HarAttributes;
use Amp\Http\Client\NetworkInterceptor;
use Amp\Http\Client\Request;
use Amp\Http\Client\Response;
use Amp\Http\HttpMessage;
use Amp\Socket\InternetAddress;
use Amp\Socket\TlsInfo;
use Amp\Sync\LocalMutex;
use Revolt\EventLoop;
use function Amp\File\filesystem;
use function Amp\File\openFile;
use function Amp\now;

final class LogHttpArchive implements ApplicationInterceptor
final class LogHttpArchive implements EventListener
{
use ForbidCloning;
use ForbidSerialization;
Expand Down Expand Up @@ -139,14 +144,12 @@ private static function formatEntry(Response $response): array

private Filesystem $filesystem;

private ?File\File $fileHandle = null;
private ?File $fileHandle = null;

private string $filePath;

private ?\Throwable $error = null;

private EventListener $eventListener;

public function __construct(string $filePath, ?Filesystem $filesystem = null)
{
if (!\class_exists(Filesystem::class)) {
Expand All @@ -155,26 +158,7 @@ public function __construct(string $filePath, ?Filesystem $filesystem = null)

$this->filePath = $filePath;
$this->fileMutex = new LocalMutex;
$this->eventListener = new RecordHarAttributes;
$this->filesystem = $filesystem ?? File\filesystem();
}

public function request(
Request $request,
Cancellation $cancellation,
DelegateHttpClient $httpClient
): Response {
if ($this->error) {
throw $this->error;
}

$this->ensureEventListenerIsRegistered($request);

$response = $httpClient->request($request, $cancellation);

EventLoop::queue(fn () => $this->writeLog($response));

return $response;
$this->filesystem = $filesystem ?? filesystem();
}

public function reset(): void
Expand Down Expand Up @@ -208,7 +192,7 @@ private function writeLog(Response $response): void
$firstEntry = $this->fileHandle === null;

if ($firstEntry) {
$this->fileHandle = $fileHandle = File\openFile($this->filePath, 'w');
$this->fileHandle = $fileHandle = openFile($this->filePath, 'w');

$header = '{"log":{"version":"1.2","creator":{"name":"amphp/http-client","version":"4.x"},"pages":[],"entries":[';

Expand All @@ -218,7 +202,7 @@ private function writeLog(Response $response): void

\assert($fileHandle !== null);

$fileHandle->seek(-3, File\Whence::Current);
$fileHandle->seek(-3, Whence::Current);
}

$json = \json_encode(self::formatEntry($response));
Expand All @@ -233,14 +217,134 @@ private function writeLog(Response $response): void
}
}

private function ensureEventListenerIsRegistered(Request $request): void
public function requestStart(Request $request): void
{
foreach ($request->getEventListeners() as $eventListener) {
if ($eventListener instanceof RecordHarAttributes) {
return; // user added it manually
}
if (!$request->hasAttribute(HarAttributes::STARTED_DATE_TIME)) {
$request->setAttribute(HarAttributes::STARTED_DATE_TIME, new \DateTimeImmutable);
}

$this->addTiming(HarAttributes::TIME_START, $request);
}

public function connectStart(Request $request): void
{
$this->addTiming(HarAttributes::TIME_CONNECT, $request);
}

public function requestHeaderStart(Request $request, Stream $stream): void
{
$address = $stream->getRemoteAddress();
$host = match (true) {
$address instanceof InternetAddress => $address->getAddress(),
default => $address->toString(),
};
if (\strrpos($host, ':')) {
$host = '[' . $host . ']';
}

$request->setAttribute(HarAttributes::SERVER_IP_ADDRESS, $host);
$this->addTiming(HarAttributes::TIME_SEND, $request);
}

public function requestBodyEnd(Request $request, Stream $stream): void
{
$this->addTiming(HarAttributes::TIME_WAIT, $request);
}

public function responseHeaderStart(Request $request, Stream $stream): void
{
$this->addTiming(HarAttributes::TIME_RECEIVE, $request);
}

public function requestEnd(Request $request, Response $response): void
{
$this->addTiming(HarAttributes::TIME_COMPLETE, $request);

EventLoop::queue(fn () => $this->writeLog($response));
}

/**
* @param non-empty-string $key
*/
private function addTiming(string $key, Request $request): void
{
if (!$request->hasAttribute($key)) {
$request->setAttribute($key, now());
}
}

public function requestFailed(Request $request, HttpException $exception): void
{
// TODO: Log error to archive
}

$request->addEventListener($this->eventListener);
public function connectEnd(Request $request, Connection $connection): void
{
// nothing to do
}

public function tlsHandshakeStart(Request $request): void
{
$this->addTiming(HarAttributes::TIME_SSL, $request);
}

public function tlsHandshakeEnd(Request $request, TlsInfo $tlsInfo): void
{
// nothing to do
}

public function requestHeaderEnd(Request $request, Stream $stream): void
{
// nothing to do
}

public function requestBodyStart(Request $request, Stream $stream): void
{
// nothing to do
}

public function requestBodyProgress(Request $request, Stream $stream): void
{
// nothing to do
}

public function responseHeaderEnd(Request $request, Stream $stream, Response $response): void
{
// nothing to do
}

public function responseBodyStart(Request $request, Stream $stream, Response $response): void
{
// nothing to do
}

public function responseBodyProgress(Request $request, Stream $stream, Response $response): void
{
// nothing to do
}

public function responseBodyEnd(Request $request, Stream $stream, Response $response): void
{
// nothing to do
}

public function applicationInterceptorStart(Request $request, ApplicationInterceptor $interceptor): void
{
// nothing to do
}

public function applicationInterceptorEnd(Request $request, ApplicationInterceptor $interceptor, Response $response): void
{
// nothing to do
}

public function networkInterceptorStart(Request $request, NetworkInterceptor $interceptor): void
{
// nothing to do
}

public function networkInterceptorEnd(Request $request, NetworkInterceptor $interceptor, Response $response): void
{
// nothing to do
}
}
Loading

0 comments on commit 23e1958

Please sign in to comment.