8000 [HttpClient] Add support for amphp/http-client v5 by nicolas-grekas · Pull Request #43 · nicolas-grekas/symfony · GitHub
[go: up one dir, main page]

Skip to content

[HttpClient] Add support for amphp/http-client v5 #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7e62355
chore: simplify PHP CS Fixer config
keradus Feb 26, 2024
c24e3e7
[Validator] Simplify `NoSuspiciousCharactersValidator`
MatTheCat Feb 26, 2024
f8611cf
minor #54062 [Validator] Simplify `NoSuspiciousCharactersValidator` (…
nicolas-grekas Feb 27, 2024
dfb41cd
[Mailer][Brevo] Remove tags from mandatory event arguments
palgalik Feb 27, 2024
182e93e
bug #54089 [Mailer] [Brevo] Remove tags from mandatory event argument…
fabpot Feb 28, 2024
9407a1b
[HttpClient] Fix deprecation on PHP 8.3
nicolas-grekas Feb 28, 2024
3a02e21
minor #54073 chore: simplify PHP CS Fixer config (keradus)
fabpot Feb 29, 2024
bb7c711
bug #54102 [HttpClient] Fix deprecation on PHP 8.3 (nicolas-grekas)
derrabus Feb 29, 2024
ae16b2d
[AssetMapper] Fix `JavaScriptImportPathCompiler` regression in regex
PhilETaylor Feb 27, 2024
91278da
bug #54079 [AssetMapper] Fix `JavaScriptImportPathCompiler` regressio…
nicolas-grekas Feb 29, 2024
5f959e2
Remove undefined variable
smnandre Mar 1, 2024
0478d54
[Clock] Add PHPUnit 10 attributes
ruudk Mar 1, 2024
ff08e9e
bug #54129 [Clock] Add attributes to support PHPUnit 10 + 11 (ruudk)
nicolas-grekas Mar 1, 2024
4155f66
[Security][Tests] Update functional tests to better reflect end-user …
llupa Feb 27, 2024
1f386a3
minor #54086 [Security][Tests] Update functional tests to better refl…
chalasr Mar 1, 2024
a184a18
Merge branch '5.4' into 6.4
wouterj Mar 2, 2024
e770fba
Merge branch '6.4' into 7.0
wouterj Mar 2, 2024
9482c97
Merge branch '7.0' into 7.1
wouterj Mar 2, 2024
9af1f34
[AssetMapper] Deprecate unused method `splitPackageNameAndFilePath`
smnandre Mar 1, 2024
31a9b35
feature #54125 [AssetMapper] Deprecate unused method `splitPackageNam…
fabpot Mar 2, 2024
4d120b8
[HttpClient] Preserve float in JsonMockResponse
Jibbarth Mar 4, 2024
24cf1eb
minor #54122 [AssetMapper] Remove undefined $path variable (smnandre)
fabpot Mar 4, 2024
2333b58
[AssetMapper] Throw exception in Javascript compiler when PCRE error
smnandre Feb 29, 2024
5f78910
bug #54113 [AssetMapper] Throw exception in Javascript compiler when …
fabpot Mar 4, 2024
3c39c04
Merge branch '6.4' into 7.0
fabpot Mar 4, 2024
f208ca7
Merge branch '7.0' into 7.1
fabpot Mar 4, 2024
fdb0340
[Mailer] Simplify MailerTestCommand
lyrixx Feb 29, 2024
9d6ee30
minor #54110 [Mailer] Simplify MailerTestCommand (lyrixx)
fabpot Mar 4, 2024
9b03f27
Update CHANGELOG for 5.4.37
fabpot Mar 4, 2024
8d1867d
Update CONTRIBUTORS for 5.4.37
fabpot Mar 4, 2024
3e40319
Update VERSION for 5.4.37
fabpot Mar 4, 2024
3f8d2f1
Merge pull request #54157 from fabpot/release-5.4.37
fabpot Mar 4, 2024
2ed7af7
Bump Symfony version to 5.4.38
fabpot Mar 4, 2024
b9da773
Update CHANGELOG for 6.4.5
fabpot Mar 4, 2024
e394573
Update VERSION for 6.4.5
fabpot Mar 4, 2024
86b66dc
Merge pull request #54158 from fabpot/release-6.4.5
fabpot Mar 4, 2024
75bafd2
Bump Symfony version to 6.4.6
fabpot Mar 4, 2024
47d722b
Update CHANGELOG for 7.0.5
fabpot Mar 4, 2024
f3ecda4
Update VERSION for 7.0.5
fabpot Mar 4, 2024
aa77387
Merge pull request #54159 from fabpot/release-7.0.5
fabpot Mar 4, 2024
cdb3fdd
Bump Symfony version to 7.0.6
fabpot Mar 4, 2024
f5b5e14
Merge branch '5.4' into 6.4
derrabus Mar 5, 2024
0523300
bug #54146 [HttpClient] Preserve float in JsonMockResponse (Jibbarth)
derrabus Mar 5, 2024
e37f57b
Merge branch '6.4' into 7.0
derrabus Mar 5, 2024
b982c71
Merge branch '7.0' into 7.1
derrabus Mar 5, 2024
dac2ea7
[HttpClient] Add support for amphp/http-client v5
nicolas-grekas May 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
[HttpClient] Add support for amphp/http-client v5
  • Loading branch information
nicolas-grekas committed Mar 6, 2024
commit dac2ea7651e07b5ff332974ecb35df110acf0e14
1 change: 1 addition & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
- php: '8.2'
mode: low-deps
- php: '8.3'
- php: '8.4'
#mode: experimental
fail-fast: false

Expand Down
7 changes: 4 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,8 @@
"symfony/yaml": "self.version"
},
"require-dev": {
"amphp/amp": "^2.5",
"amphp/http-client": "^4.2.1",
"amphp/http-tunnel": "^1.0",
"amphp/http-client": "^4.2.1|^5.0",
"amphp/http-tunnel": "^1.0|^2.0",
"async-aws/ses": "^1.0",
"async-aws/sqs": "^1.0|^2.0",
"async-aws/sns": "^1.0",
Expand All @@ -151,6 +150,7 @@
"psr/http-client": "^1.0",
"psr/simple-cache": "^1.0|^2.0|^3.0",
"seld/jsonlint": "^1.10",
"symfony/amphp-http-client-meta": "^1.0|^2.0",
"symfony/mercure-bundle": "^0.3",
"symfony/phpunit-bridge": "^6.4|^7.0",
"symfony/runtime": "self.version",
Expand All @@ -163,6 +163,7 @@
},
"conflict": {
"ext-psr": "<1.1|>=2",
"amphp/amp": "<2.5",
"async-aws/core": "<1.5",
"doctrine/dbal": "<3.6",
"doctrine/orm": "<2.15",
Expand Down
50 changes: 35 additions & 15 deletions src/Symfony/Component/HttpClient/AmpHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,20 @@
namespace Symfony\Component\HttpClient;

use Amp\CancelledException;
use Amp\DeferredFuture;
use Amp\Http\Client\DelegateHttpClient;
use Amp\Http\Client\InterceptedHttpClient;
use Amp\Http\Client\PooledHttpClient;
use Amp\Http\Client\Request;
use Amp\Http\HttpMessage;
use Amp\Http\Tunnel\Http1TunnelConnector;
use Amp\Promise;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Component\HttpClient\Internal\AmpClientState;
use Symfony\Component\HttpClient\Response\AmpResponse;
use Symfony\Component\HttpClient\Internal\AmpClientStateV4;
use Symfony\Component\HttpClient\Internal\AmpClientStateV5;
use Symfony\Component\HttpClient\Response\AmpResponseV4;
use Symfony\Component\HttpClient\Response\AmpResponseV5;
use Symfony\Component\HttpClient\Response\ResponseStream;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
Expand All @@ -33,8 +36,8 @@
throw new \LogicException('You cannot use "Symfony\Component\HttpClient\AmpHttpClient" as the "amphp/http-client" package is not installed. Try running "composer require amphp/http-client:^4.2.1".');
}

if (!interface_exists(Promise::class)) {
throw new \LogicException('You cannot use "Symfony\Component\HttpClient\AmpHttpClient" as the installed "amphp/http-client" is not compatible with this version of "symfony/http-client". Try downgrading "amphp/http-client" to "^4.2.1".');
if (\PHP_VERSION_ID < 80400 && is_subclass_of(Request::class, HttpMessage::class)) {
throw new \LogicException('Using "Symfony\Component\HttpClient\AmpHttpClient" with amphp/http-client >= 5 requires PHP >= 8.4. Try running "composer require amphp/http-client:^4.2.1" or upgrade to PHP >= 8.4.');
}

/**
Expand All @@ -53,7 +56,7 @@ final class AmpHttpClient implements HttpClientInterface, LoggerAwareInterface,

private array $defaultOptions = self::OPTIONS_DEFAULTS;
private static array $emptyDefaults = self::OPTIONS_DEFAULTS;
private AmpClientState $multi;
private AmpClientStateV4|AmpClientStateV5 $multi;

/**
* @param array $defaultOptions Default requests' options
Expand All @@ -72,7 +75,11 @@ public function __construct(array $defaultOptions = [], ?callable $clientConfigu
[, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions);
}

$this->multi = new AmpClientState($clientConfigurator, $maxHostConnections, $maxPendingPushes, $this->logger);
if (is_subclass_of(Request::class, HttpMessage::class)) {
$this->multi = new AmpClientStateV5($clientConfigurator, $maxHostConnections, $maxPendingPushes, $this->logger);
} else {
$this->multi = new AmpClientStateV4($clientConfigurator, $maxHostConnections, $maxPendingPushes, $this->logger);
}
}

/**
Expand Down Expand Up @@ -132,9 +139,10 @@ public function request(string $method, string $url, array $options = []): Respo
$request->addHeader($h[0], $h[1]);
}

$request->setTcpConnectTimeout(1000 * $options['timeout']);
$request->setTlsHandshakeTimeout(1000 * $options['timeout']);
$request->setTransferTimeout(1000 * $options['max_duration']);
$coef = $request instanceof HttpMessage ? 1 : 1000;
$request->setTcpConnectTimeout($coef * $options['timeout']);
$request->setTlsHandshakeTimeout($coef * $options['timeout& B421 #39;]);
$request->setTransferTimeout($coef * $options['max_duration']);
if (method_exists($request, 'setInactivityTimeout')) {
$request->setInactivityTimeout(0);
}
Expand All @@ -145,25 +153,37 @@ public function request(string $method, string $url, array $options = []): Respo
$request->setHeader('Authorization', 'Basic '.base64_encode(implode(':', $auth)));
}

return new AmpResponse($this->multi, $request, $options, $this->logger);
if ($request instanceof HttpMessage) {
return new AmpResponseV5($this->multi, $request, $options, $this->logger);
}

return new AmpResponseV4($this->multi, $request, $options, $this->logger);
}

public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface
{
if ($responses instanceof AmpResponse) {
if ($responses instanceof AmpResponseV4 || $responses instanceof AmpResponseV5) {
$responses = [$responses];
}

return new ResponseStream(AmpResponse::stream($responses, $timeout));
if ($this->multi instanceof AmpClientStateV5) {
return new ResponseStream(AmpResponseV5::stream($responses, $timeout));
}

return new ResponseStream(AmpResponseV4::stream($responses, $timeout));
}

public function reset(): void
{
$this->multi->dnsCache = [];

foreach ($this->multi->pushedResponses as $authority => $pushedResponses) {
foreach ($this->multi->pushedResponses as $pushedResponses) {
foreach ($pushedResponses as [$pushedUrl, $pushDeferred]) {
$pushDeferred->fail(new CancelledException());
if ($pushDeferred instanceof DeferredFuture) {
$pushDeferred->error(new CancelledException());
} else {
$pushDeferred->fail(new CancelledException());
}

$this->logger?->debug(sprintf('Unused pushed response: "%s"', $pushedUrl));
}
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/HttpClient/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CHANGELOG
* Allow mocking `start_time` info in `MockResponse`
* Add `MockResponse::fromFile()` and `JsonMockResponse::fromFile()` methods to help using fixtures files
* Add `ThrottlingHttpClient` to enable limiting the number of requests within a certain period
* Add support for amphp/http-client v5 on PHP 8.4+

7.0
---
Expand Down
6 changes: 3 additions & 3 deletions src/Symfony/Component/HttpClient/HttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

namespace Symfony\Component\HttpClient;

use Amp\Http\Client\Connection\ConnectionLimitingPool;
use Amp\Promise;
use Amp\Http\Client\Request as AmpRequest;
use Amp\Http\HttpMessage;
use Symfony\Contracts\HttpClient\HttpClientInterface;

/**
Expand All @@ -31,7 +31,7 @@ final class HttpClient
*/
public static function create(array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 50): HttpClientInterface
{
if ($amp = class_exists(ConnectionLimitingPool::class) && interface_exists(Promise::class)) {
if ($amp = class_exists(AmpRequest::class) && (\PHP_VERSION_ID >= 80400 || is_subclass_of(AmpRequest::class, HttpMessage::class))) {
if (!\extension_loaded('curl')) {
return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*
* @internal
*/
class AmpBody implements RequestBody, InputStream
class AmpBodyV4 implements RequestBody, InputStream
{
private ResourceInputStream|\Closure|string $body;
private array $info;
Expand Down
150 changes: 150 additions & 0 deletions src/Symfony/Component/HttpClient/Internal/AmpBodyV5.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpClient\Internal;

use Amp\ByteStream\ReadableBuffer;
use Amp\ByteStream\ReadableIterableStream;
use Amp\ByteStream\ReadableResourceStream;
use Amp\ByteStream\ReadableStream;
use Amp\Cancellation;
use Amp\Http\Client\HttpContent;
use Symfony\Component\HttpClient\Exception\TransportException;

/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class AmpBodyV5 implements HttpContent, ReadableStream, \IteratorAggregate
{
private ReadableStream $body;
private ?string $content;
private array $info;
private ?int $offset = 0;
private int $length = -1;
private ?int $uploaded = null;

/**
* @param \Closure|resource|string $body
*/
public function __construct(
$body,
&$info,
private \Closure $onProgress,
) {
$this->info = &$info;

if (\is_resource($body)) {
$this->offset = ftell($body);
$this->length = fstat($body)['size'];
$this->body = new ReadableResourceStream($body);
} elseif (\is_string($body)) {
$this->length = \strlen($body);
$this->body = new ReadableBuffer($body);
$this->content = $body;
} else {
$this->body = new ReadableIterableStream((static function () use ($body) {
while ('' !== $data = ($body)(16372)) {
if (!\is_string($data)) {
throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data)));
}

yield $data;
}
})());
}
}

public function getContent(): ReadableStream
{
if (null !== $this->uploaded) {
$this->uploaded = null;

if (\is_string($this->body)) {
$this->offset = 0;
} elseif ($this->body instanceof ReadableResourceStream) {
fseek($this->body->getResource(), $this->offset);
}
}

return $this;
}

public function getContentType(): ?string
{
return null;
}

public function getContentLength(): ?int
{
return 0 <= $this->length ? $this->length - $this->offset : null;
}

public function read(Cancellation $cancellation = null): ?string
{
$this->info['size_upload'] += $this->uploaded;
$this->uploaded = 0;
($this->onProgress)();

if (null !== $data = $this->body->read($cancellation)) {
$this->uploaded = \strlen($data);
} else {
$this->info['upload_content_length'] = $this->info['size_upload'];
}

return $data;
}

public function isReadable(): bool
{
return $this->body->isReadable();
}

public function close(): void
{
$this->body->close();
}

public function isClosed(): bool
{
return $this->body->isClosed();
}

public function onClose(\Closure $onClose): void
{
$this->body->onClose($onClose);
}

public function getIterator(): \Traversable
{
return $this->body;
}

public static function rewind(HttpContent $body): HttpContent
{
if (!$body instanceof self) {
return $body;
}

$body->uploaded = null;

if ($body->body instanceof ReadableResourceStream && !$body->body->isClosed()) {
fseek($body->body->getResource(), $body->offset);
}

if ($body->body instanceof ReadableBuffer) {
return new $body($body->content, $body->info, $body->onProgress);
}

return $body;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
*
* @internal
*/
final class AmpClientState extends ClientState
final class AmpClientStateV4 extends ClientState
{
public array $dnsCache = [];
public int $responseCount = 0;
Expand Down Expand Up @@ -90,7 +90,7 @@ public function request(array $options, Request $request, CancellationToken $can
$info['peer_certificate_chain'] = [];
}

$request->addEventListener(new AmpListener($info, $options['peer_fingerprint']['pin-sha256'] ?? [], $onProgress, $handle));
$request->addEventListener(new AmpListenerV4($info, $options['peer_fingerprint']['pin-sha256'] ?? [], $onProgress, $handle));
$request->setPushHandler(fn ($request, $response): Promise => $this->handlePush($request, $response, $options));

($request->hasHeader('content-length') ? new Success((int) $request->getHeader('content-length')) : $request->getBody()->getBodyLength())
Expand Down Expand Up @@ -157,7 +157,7 @@ public function connect(string $uri, ?ConnectContext $context = null, ?Cancellat
return $result;
}
};
$connector->connector = new DnsConnector(new AmpResolver($this->dnsCache));
$connector->connector = new DnsConnector(new AmpResolverV4($this->dnsCache));

$context = (new ConnectContext())
->withTcpNoDelay()
Expand Down
Loading
0