X-Git-Url: https://yaffs.net/gitweb/?a=blobdiff_plain;f=vendor%2Fsymfony%2Fhttp-kernel%2FHttpCache%2FHttpCache.php;h=44405209d4a5c6f91734a731014c9e1b23101f6e;hb=5b8bb166bfa98770daef9de5c127fc2e6ef02340;hp=297e98a240ac85d5c61491b51123a3e17b3d7482;hpb=9917807b03b64faf00f6a1f29dcb6eafc454efa5;p=yaffs-website diff --git a/vendor/symfony/http-kernel/HttpCache/HttpCache.php b/vendor/symfony/http-kernel/HttpCache/HttpCache.php index 297e98a24..44405209d 100644 --- a/vendor/symfony/http-kernel/HttpCache/HttpCache.php +++ b/vendor/symfony/http-kernel/HttpCache/HttpCache.php @@ -15,10 +15,10 @@ namespace Symfony\Component\HttpKernel\HttpCache; -use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\HttpKernel\TerminableInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\TerminableInterface; /** * Cache provides HTTP caching. @@ -69,11 +69,6 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * the cache can serve a stale response when an error is encountered (default: 60). * This setting is overridden by the stale-if-error HTTP Cache-Control extension * (see RFC 5861). - * - * @param HttpKernelInterface $kernel An HttpKernelInterface instance - * @param StoreInterface $store A Store instance - * @param SurrogateInterface $surrogate A SurrogateInterface instance - * @param array $options An array of options */ public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = array()) { @@ -170,30 +165,35 @@ class HttpCache implements HttpKernelInterface, TerminableInterface // FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism if (HttpKernelInterface::MASTER_REQUEST === $type) { $this->traces = array(); - $this->request = $request; + // Keep a clone of the original request for surrogates so they can access it. + // We must clone here to get a separate instance because the application will modify the request during + // the application flow (we know it always does because we do ourselves by setting REMOTE_ADDR to 127.0.0.1 + // and adding the X-Forwarded-For header, see HttpCache::forward()). + $this->request = clone $request; if (null !== $this->surrogate) { $this->surrogateCacheStrategy = $this->surrogate->createCacheStrategy(); } } - $path = $request->getPathInfo(); - if ($qs = $request->getQueryString()) { - $path .= '?'.$qs; - } - $this->traces[$request->getMethod().' '.$path] = array(); + $this->traces[$this->getTraceKey($request)] = array(); if (!$request->isMethodSafe(false)) { $response = $this->invalidate($request, $catch); } elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) { $response = $this->pass($request, $catch); + } elseif ($this->options['allow_reload'] && $request->isNoCache()) { + /* + If allow_reload is configured and the client requests "Cache-Control: no-cache", + reload the cache by fetching a fresh response and caching it (if possible). + */ + $this->record($request, 'reload'); + $response = $this->fetch($request, $catch); } else { $response = $this->lookup($request, $catch); } $this->restoreResponseBody($request, $response); - $response->setDate(\DateTime::createFromFormat('U', time(), new \DateTimeZone('UTC'))); - if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) { $response->headers->set('X-Symfony-Cache', $this->getLog()); } @@ -291,7 +291,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * it triggers "miss" processing. * * @param Request $request A Request instance - * @param bool $catch whether to process exceptions + * @param bool $catch Whether to process exceptions * * @return Response A Response instance * @@ -299,13 +299,6 @@ class HttpCache implements HttpKernelInterface, TerminableInterface */ protected function lookup(Request $request, $catch = false) { - // if allow_reload and no-cache Cache-Control, allow a cache reload - if ($this->options['allow_reload'] && $request->isNoCache()) { - $this->record($request, 'reload'); - - return $this->fetch($request, $catch); - } - try { $entry = $this->store->lookup($request); } catch (\Exception $e) { @@ -377,7 +370,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface // return the response and not the cache entry if the response is valid but not cached $etag = $response->getEtag(); - if ($etag && in_array($etag, $requestEtags) && !in_array($etag, $cachedEtags)) { + if ($etag && \in_array($etag, $requestEtags) && !\in_array($etag, $cachedEtags)) { return $response; } @@ -403,12 +396,11 @@ class HttpCache implements HttpKernelInterface, TerminableInterface } /** - * Forwards the Request to the backend and determines whether the response should be stored. - * - * This methods is triggered when the cache missed or a reload is required. + * Unconditionally fetches a fresh response from the backend and + * stores it in the cache if is cacheable. * * @param Request $request A Request instance - * @param bool $catch whether to process exceptions + * @param bool $catch Whether to process exceptions * * @return Response A Response instance */ @@ -437,6 +429,9 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Forwards the Request to the backend and returns the Response. * + * All backend requests (cache passes, fetches, cache validations) + * run through this method. + * * @param Request $request A Request instance * @param bool $catch Whether to catch exceptions or not * @param Response $entry A Response instance (the stale entry if present, null otherwise) @@ -449,30 +444,11 @@ class HttpCache implements HttpKernelInterface, TerminableInterface $this->surrogate->addSurrogateCapability($request); } - // modify the X-Forwarded-For header if needed - $forwardedFor = $request->headers->get('X-Forwarded-For'); - if ($forwardedFor) { - $request->headers->set('X-Forwarded-For', $forwardedFor.', '.$request->server->get('REMOTE_ADDR')); - } else { - $request->headers->set('X-Forwarded-For', $request->server->get('REMOTE_ADDR')); - } - - // fix the client IP address by setting it to 127.0.0.1 as HttpCache - // is always called from the same process as the backend. - $request->server->set('REMOTE_ADDR', '127.0.0.1'); - - // make sure HttpCache is a trusted proxy - if (!in_array('127.0.0.1', $trustedProxies = Request::getTrustedProxies())) { - $trustedProxies[] = '127.0.0.1'; - Request::setTrustedProxies($trustedProxies, method_exists('Request', 'getTrustedHeaderSet') ? Request::getTrustedHeaderSet() : -1); - } - // always a "master" request (as the real master request can be in cache) - $response = $this->kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, $catch); - // FIXME: we probably need to also catch exceptions if raw === true + $response = SubRequestHandler::handle($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $catch); // we don't implement the stale-if-error on Requests, which is nonetheless part of the RFC - if (null !== $entry && in_array($response->getStatusCode(), array(500, 502, 503, 504))) { + if (null !== $entry && \in_array($response->getStatusCode(), array(500, 502, 503, 504))) { if (null === $age = $entry->headers->getCacheControlDirective('stale-if-error')) { $age = $this->options['stale_if_error']; } @@ -484,6 +460,17 @@ class HttpCache implements HttpKernelInterface, TerminableInterface } } + /* + RFC 7231 Sect. 7.1.1.2 says that a server that does not have a reasonably accurate + clock MUST NOT send a "Date" header, although it MUST send one in most other cases + except for 1xx or 5xx responses where it MAY do so. + + Anyway, a client that received a message without a "Date" header MUST add it. + */ + if (!$response->headers->has('Date')) { + $response->setDate(\DateTime::createFromFormat('U', time())); + } + $this->processResponseBody($request, $response); if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) { @@ -498,9 +485,6 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Checks whether the cache entry is "fresh enough" to satisfy the Request. * - * @param Request $request A Request instance - * @param Response $entry A Response instance - * * @return bool true if the cache entry if fresh enough, false otherwise */ protected function isFreshEnough(Request $request, Response $entry) @@ -519,9 +503,6 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Locks a Request during the call to the backend. * - * @param Request $request A Request instance - * @param Response $entry A Response instance - * * @return bool true if the cache entry can be returned even if it is staled, false otherwise */ protected function lock(Request $request, Response $entry) @@ -529,64 +510,48 @@ class HttpCache implements HttpKernelInterface, TerminableInterface // try to acquire a lock to call the backend $lock = $this->store->lock($request); + if (true === $lock) { + // we have the lock, call the backend + return false; + } + // there is already another process calling the backend - if (true !== $lock) { - // check if we can serve the stale entry - if (null === $age = $entry->headers->getCacheControlDirective('stale-while-revalidate')) { - $age = $this->options['stale_while_revalidate']; - } - if (abs($entry->getTtl()) < $age) { - $this->record($request, 'stale-while-revalidate'); + // May we serve a stale response? + if ($this->mayServeStaleWhileRevalidate($entry)) { + $this->record($request, 'stale-while-revalidate'); - // server the stale response while there is a revalidation - return true; - } - - // wait for the lock to be released - $wait = 0; - while ($this->store->isLocked($request) && $wait < 5000000) { - usleep(50000); - $wait += 50000; - } + return true; + } - if ($wait < 5000000) { - // replace the current entry with the fresh one - $new = $this->lookup($request); - $entry->headers = $new->headers; - $entry->setContent($new->getContent()); - $entry->setStatusCode($new->getStatusCode()); - $entry->setProtocolVersion($new->getProtocolVersion()); - foreach ($new->headers->getCookies() as $cookie) { - $entry->headers->setCookie($cookie); - } - } else { - // backend is slow as hell, send a 503 response (to avoid the dog pile effect) - $entry->setStatusCode(503); - $entry->setContent('503 Service Unavailable'); - $entry->headers->set('Retry-After', 10); + // wait for the lock to be released + if ($this->waitForLock($request)) { + // replace the current entry with the fresh one + $new = $this->lookup($request); + $entry->headers = $new->headers; + $entry->setContent($new->getContent()); + $entry->setStatusCode($new->getStatusCode()); + $entry->setProtocolVersion($new->getProtocolVersion()); + foreach ($new->headers->getCookies() as $cookie) { + $entry->headers->setCookie($cookie); } - - return true; + } else { + // backend is slow as hell, send a 503 response (to avoid the dog pile effect) + $entry->setStatusCode(503); + $entry->setContent('503 Service Unavailable'); + $entry->headers->set('Retry-After', 10); } - // we have the lock, call the backend - return false; + return true; } /** * Writes the Response to the cache. * - * @param Request $request A Request instance - * @param Response $response A Response instance - * * @throws \Exception */ protected function store(Request $request, Response $response) { - if (!$response->headers->has('Date')) { - $response->setDate(\DateTime::createFromFormat('U', time())); - } try { $this->store->write($request, $response); @@ -607,20 +572,9 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Restores the Response body. - * - * @param Request $request A Request instance - * @param Response $response A Response instance */ private function restoreResponseBody(Request $request, Response $response) { - if ($request->isMethod('HEAD') || 304 === $response->getStatusCode()) { - $response->setContent(null); - $response->headers->remove('X-Body-Eval'); - $response->headers->remove('X-Body-File'); - - return; - } - if ($response->headers->has('X-Body-Eval')) { ob_start(); @@ -633,10 +587,14 @@ class HttpCache implements HttpKernelInterface, TerminableInterface $response->setContent(ob_get_clean()); $response->headers->remove('X-Body-Eval'); if (!$response->headers->has('Transfer-Encoding')) { - $response->headers->set('Content-Length', strlen($response->getContent())); + $response->headers->set('Content-Length', \strlen($response->getContent())); } } elseif ($response->headers->has('X-Body-File')) { - $response->setContent(file_get_contents($response->headers->get('X-Body-File'))); + // Response does not include possibly dynamic content (ESI, SSI), so we need + // not handle the content for HEAD requests + if (!$request->isMethod('HEAD')) { + $response->setContent(file_get_contents($response->headers->get('X-Body-File'))); + } } else { return; } @@ -655,8 +613,6 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * Checks if the Request includes authorization or other sensitive information * that should cause the Response to be considered private by default. * - * @param Request $request A Request instance - * * @return bool true if the Request is private, false otherwise */ private function isPrivateRequest(Request $request) @@ -665,7 +621,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface $key = strtolower(str_replace('HTTP_', '', $key)); if ('cookie' === $key) { - if (count($request->cookies->all())) { + if (\count($request->cookies->all())) { return true; } } elseif ($request->headers->has($key)) { @@ -683,11 +639,61 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * @param string $event The event name */ private function record(Request $request, $event) + { + $this->traces[$this->getTraceKey($request)][] = $event; + } + + /** + * Calculates the key we use in the "trace" array for a given request. + * + * @param Request $request + * + * @return string + */ + private function getTraceKey(Request $request) { $path = $request->getPathInfo(); if ($qs = $request->getQueryString()) { $path .= '?'.$qs; } - $this->traces[$request->getMethod().' '.$path][] = $event; + + return $request->getMethod().' '.$path; + } + + /** + * Checks whether the given (cached) response may be served as "stale" when a revalidation + * is currently in progress. + * + * @param Response $entry + * + * @return bool true when the stale response may be served, false otherwise + */ + private function mayServeStaleWhileRevalidate(Response $entry) + { + $timeout = $entry->headers->getCacheControlDirective('stale-while-revalidate'); + + if (null === $timeout) { + $timeout = $this->options['stale_while_revalidate']; + } + + return abs($entry->getTtl()) < $timeout; + } + + /** + * Waits for the store to release a locked entry. + * + * @param Request $request The request to wait for + * + * @return bool true if the lock was released before the internal timeout was hit; false if the wait timeout was exceeded + */ + private function waitForLock(Request $request) + { + $wait = 0; + while ($this->store->isLocked($request) && $wait < 100) { + usleep(50000); + ++$wait; + } + + return $wait < 100; } }