diff --git a/CHANGELOG.md b/CHANGELOG.md index 947a8f9..f81e4d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [Unreleased] + +### Added + +- Internal `Innmind\HttpTransport\Config::asAsync()` + +### Deprecated + +- `Innmind\HttpTransport\Transport::async()` + ## 9.2.0 - 2026-08-16 ### Changed diff --git a/src/Config.php b/src/Config.php index 2b3b4c0..17bf9db 100644 --- a/src/Config.php +++ b/src/Config.php @@ -3,7 +3,13 @@ namespace Innmind\HttpTransport; +use Innmind\HttpTransport\Config\Async; +use Innmind\IO\IO; use Innmind\Url\Url; +use Innmind\Time\{ + Clock, + Halt, +}; use Innmind\Immutable\Maybe; /** @@ -14,11 +20,13 @@ final class Config /** * @param Maybe> $maxConcurrency * @param Maybe $proxy + * @param Maybe $async, */ private function __construct( private Maybe $maxConcurrency, private bool $verifySSL, private Maybe $proxy, + private Maybe $async, ) { } @@ -31,11 +39,14 @@ public static function new(): self $maxConcurrency = Maybe::nothing(); /** @var Maybe */ $proxy = Maybe::nothing(); + /** @var Maybe */ + $async = Maybe::nothing(); return new self( $maxConcurrency, true, $proxy, + $async, ); } @@ -49,6 +60,7 @@ public function limitConcurrencyTo(int $max): self Maybe::just($max), $this->verifySSL, $this->proxy, + $this->async, ); } @@ -63,6 +75,7 @@ public function disableSSLVerification(): self $this->maxConcurrency, false, $this->proxy, + $this->async, ); } @@ -73,6 +86,25 @@ public function throughProxy(Url $proxy): self $this->maxConcurrency, $this->verifySSL, Maybe::just($proxy), + $this->async, + ); + } + + #[\NoDiscard] + public function asAsync( + Clock $clock, + Halt $halt, + IO $io, + ): self { + return new self( + $this->maxConcurrency, + $this->verifySSL, + $this->proxy, + Maybe::just(new Async( + $clock, + $halt, + $io, + )), ); } @@ -103,4 +135,14 @@ public function proxy(): Maybe { return $this->proxy; } + + /** + * @internal + * + * @return Maybe + */ + public function async(): Maybe + { + return $this->async; + } } diff --git a/src/Config/Async.php b/src/Config/Async.php new file mode 100644 index 0000000..6ca0c92 --- /dev/null +++ b/src/Config/Async.php @@ -0,0 +1,37 @@ +clock; + } + + public function halt(): Halt + { + return $this->halt; + } + + public function io(): IO + { + return $this->io; + } +} diff --git a/src/Transport.php b/src/Transport.php index 055e26b..d59d35d 100644 --- a/src/Transport.php +++ b/src/Transport.php @@ -95,6 +95,7 @@ public static function logger( /** * @internal + * @deprecated * * @param callable(): void $heartbeat */ diff --git a/src/Transport/CircuitBreaker.php b/src/Transport/CircuitBreaker.php index c527b1d..be6c4b7 100644 --- a/src/Transport/CircuitBreaker.php +++ b/src/Transport/CircuitBreaker.php @@ -4,6 +4,7 @@ namespace Innmind\HttpTransport\Transport; use Innmind\HttpTransport\{ + Config, Success, ServerError, ConnectionFailed, @@ -78,14 +79,28 @@ public static function of( #[\Override] public function map(callable $map): self { + $fulfill = $this->fulfill->map($map); + return new self( - $this->fulfill->map($map), - $this->clock, + $fulfill, + $fulfill->config()->async()->match( + static fn($async) => $async->clock(), + fn() => $this->clock, + ), $this->delayBeforeRetry, Map::of(), ); } + /** + * @psalm-mutation-free + */ + #[\Override] + public function config(): Config + { + return $this->fulfill->config(); + } + private function open( Request $request, ServerError|ConnectionFailed $error, diff --git a/src/Transport/Curl.php b/src/Transport/Curl.php index 610e72d..db1da72 100644 --- a/src/Transport/Curl.php +++ b/src/Transport/Curl.php @@ -114,13 +114,25 @@ public function map(callable $map): self return new self( $config, $this->headerFactory, - $this->io, + $config->async()->match( + static fn($async) => $async->io(), + fn() => $this->io, + ), Concurrency::new($config->maxConcurrency()->match( static fn($max) => $max, static fn() => null, )), - $this->timeout, - $this->heartbeat, + $config->async()->match( + static fn() => Period::millisecond(10), // this is blocking the active task so it needs to be low + fn() => $this->timeout, + ), + $config + ->async() + ->map(static fn($async) => $async->halt()) + ->match( + static fn($halt) => static fn() => $halt(Period::millisecond(1))->unwrap(), // this allows to jump between tasks + fn() => $this->heartbeat, + ), !$config->verifySSL(), $config->proxy()->match( static fn($proxy) => $proxy, @@ -128,4 +140,13 @@ public function map(callable $map): self ), ); } + + /** + * @psalm-mutation-free + */ + #[\Override] + public function config(): Config + { + return $this->config; + } } diff --git a/src/Transport/ExponentialBackoff.php b/src/Transport/ExponentialBackoff.php index 33a5162..d962bf3 100644 --- a/src/Transport/ExponentialBackoff.php +++ b/src/Transport/ExponentialBackoff.php @@ -4,6 +4,7 @@ namespace Innmind\HttpTransport\Transport; use Innmind\HttpTransport\{ + Config, Success, Information, Redirection, @@ -80,12 +81,26 @@ public static function of( #[\Override] public function map(callable $map): self { + $fulfill = $this->fulfill->map($map); + return self::of( - $this->fulfill->map($map), - $this->halt, + $fulfill, + $fulfill->config()->async()->match( + static fn($async) => $async->halt(), + fn() => $this->halt, + ), ); } + /** + * @psalm-mutation-free + */ + #[\Override] + public function config(): Config + { + return $this->fulfill->config(); + } + /** * @param Sequence $retries * diff --git a/src/Transport/FollowRedirections.php b/src/Transport/FollowRedirections.php index c628124..0dad84c 100644 --- a/src/Transport/FollowRedirections.php +++ b/src/Transport/FollowRedirections.php @@ -4,6 +4,7 @@ namespace Innmind\HttpTransport\Transport; use Innmind\HttpTransport\{ + Config, Success, Redirection, }; @@ -59,6 +60,15 @@ public function map(callable $map): self return self::of($this->fulfill->map($map)); } + /** + * @psalm-mutation-free + */ + #[\Override] + public function config(): Config + { + return $this->fulfill->config(); + } + /** * @param int<0, max> $hops * diff --git a/src/Transport/Implementation.php b/src/Transport/Implementation.php index bc50615..c07b600 100644 --- a/src/Transport/Implementation.php +++ b/src/Transport/Implementation.php @@ -36,4 +36,10 @@ public function __invoke(Request $request): Either; */ #[\NoDiscard] public function map(callable $map): self; + + /** + * @psalm-mutation-free + */ + #[\NoDiscard] + public function config(): Config; } diff --git a/src/Transport/Logger.php b/src/Transport/Logger.php index 4e0f08b..7b91f11 100644 --- a/src/Transport/Logger.php +++ b/src/Transport/Logger.php @@ -4,6 +4,7 @@ namespace Innmind\HttpTransport\Transport; use Innmind\HttpTransport\{ + Config, Success, Information, Redirection, @@ -62,6 +63,15 @@ public function map(callable $map): self ); } + /** + * @psalm-mutation-free + */ + #[\Override] + public function config(): Config + { + return $this->fulfill->config(); + } + private function logRequest(Request $request): string { $this->logger->debug( diff --git a/src/Transport/Via.php b/src/Transport/Via.php index ff2372a..2a7670e 100644 --- a/src/Transport/Via.php +++ b/src/Transport/Via.php @@ -5,6 +5,7 @@ use Innmind\HttpTransport\Success; use Innmind\Http\Request; +use Innmind\HttpTransport\Config; use Innmind\Immutable\Either; /** @@ -18,6 +19,7 @@ final class Via implements Implementation */ private function __construct( private \Closure $fulfill, + private ?Config $config, ) { } @@ -33,7 +35,7 @@ public function __invoke(Request $request): Either public static function of(callable $fulfill): self { // todo support exposing a Config to the callable ? - return new self(\Closure::fromCallable($fulfill)); + return new self(\Closure::fromCallable($fulfill), null); } /** @@ -42,6 +44,19 @@ public static function of(callable $fulfill): self #[\Override] public function map(callable $map): self { - return $this; + /** @psalm-suppress ImpureFunctionCall */ + return new self( + $this->fulfill, + $map($this->config()), + ); + } + + /** + * @psalm-mutation-free + */ + #[\Override] + public function config(): Config + { + return $this->config ?? Config::new(); } }