Skip to content

Commit 48133f8

Browse files
committed
minor symfony#52070 [HttpFoundation] Cache trusted values (Toflar)
This PR was squashed before being merged into the 6.4 branch. Discussion ---------- [HttpFoundation] Cache trusted values | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT Found another potential performance bottleneck. I personally detected it in the context of the `RouterListener` that sets the current request context and resets it, after the request is finished: 1. `onKernelRequest`: https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php#L100 (then calls https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/Routing/RequestContext.php#L70). 2. `onKernelFinishRequest`: https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php#L93 (also calls `RequestContext::fromRequest()`). The subsequent `RequestContext::fromRequest()` always calls `$request->getPort()` and `$request->isSecure()` where the trusted values are re-evaluated over and over again although as long as you don't change the trusted headers, they will always be the same. I noticed it in the context of the routing process because I use quite a few subrequests but I think the optimization can affect many parts of an application. It's true for all the request methods that rely on the trusted headers like `$request->getHost()`, `$request->getClientIps()`, `$request->getBaseUrl()` etc. So I figured, it would be best to cache the internal `getTrustedValues()` so all of those methods are optimized at once. This method is a rather heavy one because [it splits headers](https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/HttpFoundation/Request.php#L2077) and [combines parts](https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/HttpFoundation/Request.php#L2081) again etc. This only needs to be done once per trusted header set. Commits ------- 73abd62 [HttpFoundation] Cache trusted values
2 parents eadd41a + 73abd62 commit 48133f8

File tree

2 files changed

+34
-4
lines changed

2 files changed

+34
-4
lines changed

src/Symfony/Component/HttpFoundation/Request.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ class Request
212212
private bool $isForwardedValid = true;
213213
private bool $isSafeContentPreferred;
214214

215+
private array $trustedValuesCache = [];
216+
215217
private static int $trustedHeaderSet = -1;
216218

217219
private const FORWARDED_PARAMS = [
@@ -1997,8 +1999,20 @@ public function isFromTrustedProxy(): bool
19971999
return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR', ''), self::$trustedProxies);
19982000
}
19992001

2002+
/**
2003+
* This method is rather heavy because it splits and merges headers, and it's called by many other methods such as
2004+
* getPort(), isSecure(), getHost(), getClientIps(), getBaseUrl() etc. Thus, we try to cache the results for
2005+
* best performance.
2006+
*/
20002007
private function getTrustedValues(int $type, string $ip = null): array
20012008
{
2009+
$cacheKey = $type."\0".((self::$trustedHeaderSet & $type) ? $this->headers->get(self::TRUSTED_HEADERS[$type]) : '');
2010+
$cacheKey .= "\0".$ip."\0".$this->headers->get(self::TRUSTED_HEADERS[self::HEADER_FORWARDED]);
2011+
2012+
if (isset($this->trustedValuesCache[$cacheKey])) {
2013+
return $this->trustedValuesCache[$cacheKey];
2014+
}
2015+
20022016
$clientValues = [];
20032017
$forwardedValues = [];
20042018

@@ -2011,7 +2025,6 @@ private function getTrustedValues(int $type, string $ip = null): array
20112025
if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && (isset(self::FORWARDED_PARAMS[$type])) && $this->headers->has(self::TRUSTED_HEADERS[self::HEADER_FORWARDED])) {
20122026
$forwarded = $this->headers->get(self::TRUSTED_HEADERS[self::HEADER_FORWARDED]);
20132027
$parts = HeaderUtils::split($forwarded, ',;=');
2014-
$forwardedValues = [];
20152028
$param = self::FORWARDED_PARAMS[$type];
20162029
foreach ($parts as $subParts) {
20172030
if (null === $v = HeaderUtils::combine($subParts)[$param] ?? null) {
@@ -2033,15 +2046,15 @@ private function getTrustedValues(int $type, string $ip = null): array
20332046
}
20342047

20352048
if ($forwardedValues === $clientValues || !$clientValues) {
2036-
return $forwardedValues;
2049+
return $this->trustedValuesCache[$cacheKey] = $forwardedValues;
20372050
}
20382051

20392052
if (!$forwardedValues) {
2040-
return $clientValues;
2053+
return $this->trustedValuesCache[$cacheKey] = $clientValues;
20412054
}
20422055

20432056
if (!$this->isForwardedValid) {
2044-
return null !== $ip ? ['0.0.0.0', $ip] : [];
2057+
return $this->trustedValuesCache[$cacheKey] = null !== $ip ? ['0.0.0.0', $ip] : [];
20452058
}
20462059
$this->isForwardedValid = false;
20472060

src/Symfony/Component/HttpFoundation/Tests/RequestTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2550,6 +2550,23 @@ public function testTrustedProxiesRemoteAddr($serverRemoteAddr, $trustedProxies,
25502550
$this->assertSame($result, Request::getTrustedProxies());
25512551
}
25522552

2553+
public function testTrustedValuesCache()
2554+
{
2555+
$request = Request::create('http://example.com/');
2556+
$request->server->set('REMOTE_ADDR', '3.3.3.3');
2557+
$request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2');
2558+
$request->headers->set('X_FORWARDED_PROTO', 'https');
2559+
2560+
$this->assertFalse($request->isSecure());
2561+
2562+
Request::setTrustedProxies(['3.3.3.3', '2.2.2.2'], Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO);
2563+
$this->assertTrue($request->isSecure());
2564+
2565+
// Header is changed, cache must not be hit now
2566+
$request->headers->set('X_FORWARDED_PROTO', 'http');
2567+
$this->assertFalse($request->isSecure());
2568+
}
2569+
25532570
public static function trustedProxiesRemoteAddr()
25542571
{
25552572
return [

0 commit comments

Comments
 (0)