Skip to content

Commit e10c89d

Browse files
bug #62803 [AssetMapper] Batch concurrent requests to prevent flooding jsdelivr (nicolas-grekas)
This PR was merged into the 6.4 branch. Discussion ---------- [AssetMapper] Batch concurrent requests to prevent flooding jsdelivr | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | Fix #62623 | License | MIT Commits ------- 3d57f5c [AssetMapper] Batch concurrent requests to prevent flooding jsdelivr
2 parents d8d73c4 + 3d57f5c commit e10c89d

File tree

4 files changed

+67
-5
lines changed

4 files changed

+67
-5
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\AssetMapper\ImportMap;
13+
14+
use Symfony\Component\HttpClient\DecoratorTrait;
15+
use Symfony\Contracts\HttpClient\HttpClientInterface;
16+
use Symfony\Contracts\HttpClient\ResponseInterface;
17+
18+
/**
19+
* @internal
20+
*/
21+
class BatchHttpClient implements HttpClientInterface
22+
{
23+
use DecoratorTrait;
24+
25+
private const BATCH_SIZE = 250;
26+
27+
private \WeakMap $pendingRequests;
28+
29+
public function request(string $method, string $url, array $options = []): ResponseInterface
30+
{
31+
$this->pendingRequests ??= new \WeakMap();
32+
$pendingRequests = [];
33+
34+
foreach ($this->pendingRequests as $response => $_) {
35+
if ($response->getInfo('http_code')) {
36+
$this->pendingRequests->offsetUnset($response);
37+
} else {
38+
$pendingRequests[] = $response;
39+
}
40+
}
41+
42+
if (\count($pendingRequests) >= self::BATCH_SIZE) {
43+
foreach ($this->client->stream($pendingRequests) as $response => $chunk) {
44+
if (!$chunk->isTimeout() && $chunk->isFirst()) {
45+
$response->getStatusCode(); // ignore 3/4/5xx
46+
$this->pendingRequests->offsetUnset($response);
47+
break;
48+
}
49+
}
50+
}
51+
52+
$response = $this->client->request($method, $url, $options);
53+
$this->pendingRequests[$response] = true;
54+
55+
return $response;
56+
}
57+
}

src/Symfony/Component/AssetMapper/ImportMap/ImportMapUpdateChecker.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,20 @@
1111

1212
namespace Symfony\Component\AssetMapper\ImportMap;
1313

14+
use Symfony\Component\HttpClient\HttpClient;
1415
use Symfony\Contracts\HttpClient\HttpClientInterface;
1516

1617
class ImportMapUpdateChecker
1718
{
1819
private const URL_PACKAGE_METADATA = 'https://registry.npmjs.org/%s';
1920

21+
private readonly HttpClientInterface $httpClient;
22+
2023
public function __construct(
2124
private readonly ImportMapConfigReader $importMapConfigReader,
22-
private readonly HttpClientInterface $httpClient,
25+
?HttpClientInterface $httpClient = null,
2326
) {
27+
$this->httpClient = new BatchHttpClient($httpClient ?? HttpClient::create());
2428
}
2529

2630
/**

src/Symfony/Component/AssetMapper/ImportMap/ImportMapVersionChecker.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ class ImportMapVersionChecker
2121
{
2222
private const PACKAGE_METADATA_PATTERN = 'https://registry.npmjs.org/%package%/%version%';
2323

24-
private HttpClientInterface $httpClient;
24+
private readonly HttpClientInterface $httpClient;
2525

2626
public function __construct(
2727
private ImportMapConfigReader $importMapConfigReader,
2828
private RemotePackageDownloader $packageDownloader,
2929
?HttpClientInterface $httpClient = null,
3030
) {
31-
$this->httpClient = $httpClient ?? HttpClient::create();
31+
$this->httpClient = new BatchHttpClient($httpClient ?? HttpClient::create());
3232
}
3333

3434
/**

src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\AssetMapper\Compiler\CssAssetUrlCompiler;
1515
use Symfony\Component\AssetMapper\Exception\RuntimeException;
16+
use Symfony\Component\AssetMapper\ImportMap\BatchHttpClient;
1617
use Symfony\Component\AssetMapper\ImportMap\ImportMapEntry;
1718
use Symfony\Component\AssetMapper\ImportMap\ImportMapType;
1819
use Symfony\Component\AssetMapper\ImportMap\PackageRequireOptions;
@@ -32,12 +33,12 @@ final class JsDelivrEsmResolver implements PackageResolverInterface
3233

3334
private const ES_MODULE_SHIMS = 'es-module-shims';
3435

35-
private HttpClientInterface $httpClient;
36+
private readonly HttpClientInterface $httpClient;
3637

3738
public function __construct(
3839
?HttpClientInterface $httpClient = null,
3940
) {
40-
$this->httpClient = $httpClient ?? HttpClient::create();
41+
$this->httpClient = new BatchHttpClient($httpClient ?? HttpClient::create());
4142
}
4243

4344
public function resolvePackages(array $packagesToRequire): array

0 commit comments

Comments
 (0)