Skip to content

Commit

Permalink
Add new event system (#334)
Browse files Browse the repository at this point in the history
Adds event listeners automatically in HttpClientBuilder.
Ensures event listeners are unique.
Turns LogHttpArchive into an event listener instead of separate listener and interceptor.

Co-authored-by: Aaron Piotrowski <[email protected]>
  • Loading branch information
kelunik and trowski authored Aug 23, 2023
1 parent fd5b005 commit a9f2beb
Show file tree
Hide file tree
Showing 31 changed files with 909 additions and 431 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: 3 additions & 1 deletion composer-require-check.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
"Amp\\File\\FilesystemException",
"Amp\\File\\Whence",
"Amp\\File\\Filesystem",
"Amp\\File\\filesystem"
"Amp\\File\\filesystem",
"events",
"processRequest"
],
"php-core-extensions": [
"Core",
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"Amp\\Http\\Client\\": "src"
},
"files": [
"src/functions.php",
"src/Internal/functions.php"
]
},
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
10 changes: 3 additions & 7 deletions src/Connection/ConnectionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@

use Amp\Cancellation;
use Amp\Http\Client\Request;
use function Amp\Http\Client\events;

interface ConnectionFactory
{
/**
* During connection establishment, the factory must call the {@see EventListener::startConnectionCreation()},
* {@see EventListener::startTlsNegotiation()}, {@see EventListener::completeTlsNegotiation()}, and
* {@see EventListener::completeConnectionCreation()} on all event listeners registered on the given request in the
* order defined by {@see Request::getEventListeners()} as appropriate (TLS events are only invoked if TLS is
* used). Before calling the next listener, the previous call must return successfully.
* Creates a new connection.
*
* Additionally, the factory may invoke {@see EventListener::startDnsResolution()} and
* {@see EventListener::completeDnsResolution()}, but is not required to implement such granular events.
* The implementation should call appropriate event handlers via {@see events()}.
*/
public function create(Request $request, Cancellation $cancellation): Connection;
}
79 changes: 31 additions & 48 deletions src/Connection/DefaultConnectionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Amp\Socket\ClientTlsContext;
use Amp\Socket\ConnectContext;
use Amp\TimeoutCancellation;
use function Amp\Http\Client\events;

final class DefaultConnectionFactory implements ConnectionFactory
{
Expand All @@ -27,13 +28,9 @@ public function __construct(?Socket\SocketConnector $connector = null, ?ConnectC
$this->connectContext = $connectContext;
}

public function create(
Request $request,
Cancellation $cancellation
): Connection {
foreach ($request->getEventListeners() as $eventListener) {
$eventListener->startConnectionCreation($request);
}
public function create(Request $request, Cancellation $cancellation): Connection
{
events()->connectStart($request);

$connector = $this->connector ?? Socket\socketConnector();
$connectContext = $this->connectContext ?? new ConnectContext;
Expand Down Expand Up @@ -106,21 +103,20 @@ public function create(
$cancellation
);
} catch (Socket\ConnectException $e) {
throw new UnprocessedRequestException(
new SocketException(\sprintf("Connection to '%s' failed", $authority), 0, $e)
);
} catch (CancelledException $e) {
throw new UnprocessedRequestException(new SocketException(\sprintf("Connection to '%s' failed", $authority), 0, $e));
} catch (CancelledException) {
// In case of a user cancellation request, throw the expected exception
$cancellation->throwIfRequested();

// Otherwise we ran into a timeout of our TimeoutCancellation
throw new UnprocessedRequestException(new TimeoutException(\sprintf(
"Connection to '%s' timed out, took longer than " . $request->getTcpConnectTimeout() . ' s',
$authority
))); // don't pass $e
throw new UnprocessedRequestException(
new TimeoutException(\sprintf("Connection to '%s' timed out, took longer than " . $request->getTcpConnectTimeout() . ' s', $authority))
);
}

if ($isHttps) {
events()->tlsHandshakeStart($request);

try {
$tlsState = $socket->getTlsState();

Expand All @@ -133,20 +129,10 @@ public function create(
);
}

foreach ($request->getEventListeners() as $eventListener) {
$eventListener->startTlsNegotiation($request);
}

$tlsCancellation = new CompositeCancellation(
$socket->setupTls(new CompositeCancellation(
$cancellation,
new TimeoutCancellation($request->getTlsHandshakeTimeout())
);

$socket->setupTls($tlsCancellation);

foreach ($request->getEventListeners() as $eventListener) {
$eventListener->completeTlsNegotiation($request);
}
));
} catch (StreamException $exception) {
$socket->close();

Expand All @@ -155,7 +141,7 @@ public function create(
$authority,
$socket->getRemoteAddress()->toString()
), 0, $exception));
} catch (CancelledException $e) {
} catch (CancelledException) {
$socket->close();

// In case of a user cancellation request, throw the expected exception
Expand All @@ -166,11 +152,13 @@ public function create(
"TLS handshake with '%s' @ '%s' timed out, took longer than " . $request->getTlsHandshakeTimeout() . ' s',
$authority,
$socket->getRemoteAddress()->toString()
))); // don't pass $e
)));
}

$tlsInfo = $socket->getTlsInfo();
if ($tlsInfo === null) {
$socket->close();

throw new UnprocessedRequestException(
new SocketException(\sprintf(
"Socket closed after TLS handshake with '%s' @ '%s'",
Expand All @@ -180,13 +168,13 @@ public function create(
);
}

events()->tlsHandshakeEnd($request, $tlsInfo);

if ($tlsInfo->getApplicationLayerProtocol() === 'h2') {
$http2Connection = new Http2Connection($socket);
$http2Connection->initialize($cancellation);

foreach ($request->getEventListeners() as $eventListener) {
$eventListener->completeConnectionCreation($request);
}
events()->connectEnd($request, $http2Connection);

return $http2Connection;
}
Expand All @@ -197,31 +185,26 @@ public function create(
$http2Connection = new Http2Connection($socket);
$http2Connection->initialize($cancellation);

foreach ($request->getEventListeners() as $eventListener) {
$eventListener->completeConnectionCreation($request);
}
events()->connectEnd($request, $http2Connection);

return $http2Connection;
}

if (!\array_intersect($request->getProtocolVersions(), ['1.0', '1.1'])) {
$socket->close();

throw new InvalidRequestException(
$request,
\sprintf(
"None of the requested protocol versions (%s) are supported by '%s' @ '%s'",
\implode(', ', $protocolVersions),
$authority,
$socket->getRemoteAddress()->toString()
)
);
throw new InvalidRequestException($request, \sprintf(
"None of the requested protocol versions (%s) are supported by '%s' @ '%s'",
\implode(', ', $protocolVersions),
$authority,
$socket->getRemoteAddress()->toString()
));
}

foreach ($request->getEventListeners() as $eventListener) {
$eventListener->completeConnectionCreation($request);
}
$http1Connection = new Http1Connection($socket);

events()->connectEnd($request, $http1Connection);

return new Http1Connection($socket);
return $http1Connection;
}
}
Loading

0 comments on commit a9f2beb

Please sign in to comment.