Skip to content

Commit f71b9e5

Browse files
authored
Merge 2.5 into master (api-platform#3856)
Merge 2.5 into master
2 parents f99cc0a + d6a7f60 commit f71b9e5

File tree

4 files changed

+164
-4
lines changed

4 files changed

+164
-4
lines changed

CHANGELOG.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,32 @@
2020
* OpenAPI: **BC** Replace all characters other than `[a-zA-Z0-9\.\-_]` to `.` in definition names to be compliant with OpenAPI 3.0 (#3669)
2121
* Add stateless ApiResource attribute
2222

23+
## 2.5.8
24+
25+
For compatibility reasons with Symfony 5.2 and PHP 8, we do not test these legacy packages nor their integration anymore:
26+
- NelmioApiDoc
27+
- FOSUserBundle
28+
29+
* PHP 8 support (#3791, #3745, #3855)
30+
* Metadata: Fix merging null values from annotations (#3711)
31+
* JsonLd: missing @type from collection using output DTOs. (#3699)
32+
* Cache: Improve PurgeHttpCacheListener performances (#3743)
33+
* Cache: Fix VarnishPurger max header length (#3843)
34+
* Identifiers: Do not denormalize the same identifier twice (#3762)
35+
* OpenAPI: Lazy load SwaggerCommand (#3802)
36+
* OpenAPI: Use Output class name instead of the Resource short name when available (#3741)
37+
* Router: replace baseurl only once (#3776)
38+
* Mercure: Publisher bug fixes (#3790, #3739)
39+
* Tests: Github action with windows, now uses setup-php (#3716 #3717, #3814)
40+
* Tests: improve overall PHPUnit and Symfony 5.2 compatibility (#3702 #3700, #3781, #3779, #3822)
41+
* Tests: improve behat compatibility (#3792, #3793)
42+
* Tests: improve JSON Schema assertions (#3807, #3803, #3804, #3806, #3817, #3829, #3830)
43+
* Tests: allow extra options in ApiTestClient (#3486)
44+
* Tests: Use `static_lambda` code style rule (#3725)
45+
* Remove legacy dependencies (#3794)
46+
* Serializer: Catch NotNormalizableValueException to UnexpectedValueEception with inputs (#3697)
47+
* Doctrine: ODM escape search terms in RegexFilter
48+
2349
## 2.5.7
2450

2551
* Compatibility with Symfony 5.1 (#3589 and #3688)
@@ -38,7 +64,6 @@
3864
* Docs:Upgrade ReDoc to version 2.0.0-rc.40 (#3693)
3965
* Docs:Upgrade GraphiQL to version 1.0.3 (#3693)
4066
* Docs:Upgrade GraphQL Playground to version 1.7.23 (#3693)
41-
>>>>>>> 2.5
4267

4368
## 2.5.6
4469

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"symfony/property-info": "^3.4 || ^4.4 || ^5.1",
2525
"symfony/serializer": "^4.4 || ^5.1",
2626
"symfony/web-link": "^4.4 || ^5.1",
27-
"willdurand/negotiation": "^2.0.3 || 3.0.x-dev"
27+
"willdurand/negotiation": "^2.0.3 || ^3.0"
2828
},
2929
"require-dev": {
3030
"behat/behat": "^3.1",

src/HttpCache/VarnishPurger.php

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@
2424
*/
2525
final class VarnishPurger implements PurgerInterface
2626
{
27+
private const DEFAULT_VARNISH_MAX_HEADER_LENGTH = 8000;
28+
2729
private $clients;
2830
private $maxHeaderLength;
2931

3032
/**
3133
* @param ClientInterface[] $clients
3234
*/
33-
public function __construct(array $clients, int $maxHeaderLength = 7500)
35+
public function __construct(array $clients, int $maxHeaderLength = self::DEFAULT_VARNISH_MAX_HEADER_LENGTH)
3436
{
3537
$this->clients = $clients;
3638
$this->maxHeaderLength = $maxHeaderLength;
@@ -86,10 +88,42 @@ private function purgeRequest(array $iris)
8688
return sprintf('(^|\,)%s($|\,)', preg_quote($iri));
8789
}, $iris);
8890

89-
$regex = \count($parts) > 1 ? sprintf('(%s)', implode(')|(', $parts)) : array_shift($parts);
91+
foreach ($this->chunkRegexParts($parts) as $regex) {
92+
$this->banRegex($regex);
93+
}
94+
}
9095

96+
private function banRegex(string $regex): void
97+
{
9198
foreach ($this->clients as $client) {
9299
$client->request('BAN', '', ['headers' => ['ApiPlatform-Ban-Regex' => $regex]]);
93100
}
94101
}
102+
103+
private function chunkRegexParts(array $parts): iterable
104+
{
105+
if (1 === \count($parts)) {
106+
yield $parts[0];
107+
108+
return;
109+
}
110+
111+
$concatenatedParts = sprintf('(%s)', implode(")\n(", $parts));
112+
113+
if (\strlen($concatenatedParts) <= $this->maxHeaderLength) {
114+
yield str_replace("\n", '|', $concatenatedParts);
115+
116+
return;
117+
}
118+
119+
$lastSeparator = strrpos(substr($concatenatedParts, 0, $this->maxHeaderLength + 1), "\n");
120+
121+
$chunk = substr($concatenatedParts, 0, $lastSeparator);
122+
123+
yield str_replace("\n", '|', $chunk);
124+
125+
$nextParts = \array_slice($parts, substr_count($chunk, "\n") + 1);
126+
127+
yield from $this->chunkRegexParts($nextParts);
128+
}
95129
}

tests/HttpCache/VarnishPurgerTest.php

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616
use ApiPlatform\Core\HttpCache\VarnishPurger;
1717
use ApiPlatform\Core\Tests\ProphecyTrait;
1818
use GuzzleHttp\ClientInterface;
19+
use GuzzleHttp\Promise\PromiseInterface;
1920
use GuzzleHttp\Psr7\Response;
21+
use LogicException;
2022
use PHPUnit\Framework\TestCase;
23+
use Psr\Http\Message\RequestInterface;
24+
use Psr\Http\Message\ResponseInterface;
2125

2226
/**
2327
* @author Kévin Dunglas <[email protected]>
@@ -60,4 +64,101 @@ public function testEmptyTags()
6064
$purger = new VarnishPurger([$clientProphecy1->reveal()]);
6165
$purger->purge([]);
6266
}
67+
68+
/**
69+
* @dataProvider provideChunkHeaderCases
70+
*/
71+
public function testItChunksHeaderToAvoidHittingVarnishLimit(int $maxHeaderLength, array $iris, array $regexesToSend)
72+
{
73+
$client = new class() implements ClientInterface {
74+
public $sentRegexes = [];
75+
76+
public function send(RequestInterface $request, array $options = []): ResponseInterface
77+
{
78+
throw new LogicException('Not implemented');
79+
}
80+
81+
public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface
82+
{
83+
throw new LogicException('Not implemented');
84+
}
85+
86+
public function request($method, $uri, array $options = []): ResponseInterface
87+
{
88+
$this->sentRegexes[] = $options['headers']['ApiPlatform-Ban-Regex'];
89+
90+
return new Response();
91+
}
92+
93+
public function requestAsync($method, $uri, array $options = []): PromiseInterface
94+
{
95+
throw new LogicException('Not implemented');
96+
}
97+
98+
public function getConfig($option = null)
99+
{
100+
throw new LogicException('Not implemented');
101+
}
102+
};
103+
104+
$purger = new VarnishPurger([$client], $maxHeaderLength);
105+
$purger->purge($iris);
106+
107+
self::assertSame($regexesToSend, $client->sentRegexes);
108+
}
109+
110+
public function provideChunkHeaderCases()
111+
{
112+
yield 'few iris' => [
113+
50,
114+
['/foo', '/bar'],
115+
['((^|\,)/foo($|\,))|((^|\,)/bar($|\,))'],
116+
];
117+
118+
yield 'iris to generate a header with exactly the maximum length' => [
119+
56,
120+
['/foo', '/bar', '/baz'],
121+
['((^|\,)/foo($|\,))|((^|\,)/bar($|\,))|((^|\,)/baz($|\,))'],
122+
];
123+
124+
yield 'iris to generate a header with exactly the maximum length and a smaller one' => [
125+
37,
126+
['/foo', '/bar', '/baz'],
127+
[
128+
'((^|\,)/foo($|\,))|((^|\,)/bar($|\,))',
129+
'(^|\,)/baz($|\,)',
130+
],
131+
];
132+
133+
yield 'with last iri too long to be part of the same header' => [
134+
50,
135+
['/foo', '/bar', '/some-longer-tag'],
136+
[
137+
'((^|\,)/foo($|\,))|((^|\,)/bar($|\,))',
138+
'(^|\,)/some\-longer\-tag($|\,)',
139+
],
140+
];
141+
142+
yield 'iris to have five headers' => [
143+
50,
144+
['/foo/1', '/foo/2', '/foo/3', '/foo/4', '/foo/5', '/foo/6', '/foo/7', '/foo/8', '/foo/9', '/foo/10'],
145+
[
146+
'((^|\,)/foo/1($|\,))|((^|\,)/foo/2($|\,))',
147+
'((^|\,)/foo/3($|\,))|((^|\,)/foo/4($|\,))',
148+
'((^|\,)/foo/5($|\,))|((^|\,)/foo/6($|\,))',
149+
'((^|\,)/foo/7($|\,))|((^|\,)/foo/8($|\,))',
150+
'((^|\,)/foo/9($|\,))|((^|\,)/foo/10($|\,))',
151+
],
152+
];
153+
154+
yield 'with varnish default limit' => [
155+
8000,
156+
array_fill(0, 1000, '/foo'),
157+
[
158+
implode('|', array_fill(0, 421, '((^|\,)/foo($|\,))')),
159+
implode('|', array_fill(0, 421, '((^|\,)/foo($|\,))')),
160+
implode('|', array_fill(0, 158, '((^|\,)/foo($|\,))')),
161+
],
162+
];
163+
}
63164
}

0 commit comments

Comments
 (0)