Mercurial > hg > isophonics-drupal-site
comparison vendor/symfony/http-kernel/HttpCache/HttpCache.php @ 14:1fec387a4317
Update Drupal core to 8.5.2 via Composer
author | Chris Cannam |
---|---|
date | Mon, 23 Apr 2018 09:46:53 +0100 |
parents | 4c8ae668cc8c |
children | c2387f117808 |
comparison
equal
deleted
inserted
replaced
13:5fb285c0d0e3 | 14:1fec387a4317 |
---|---|
67 * | 67 * |
68 * * stale_if_error Specifies the default number of seconds (the granularity is the second) during which | 68 * * stale_if_error Specifies the default number of seconds (the granularity is the second) during which |
69 * the cache can serve a stale response when an error is encountered (default: 60). | 69 * the cache can serve a stale response when an error is encountered (default: 60). |
70 * This setting is overridden by the stale-if-error HTTP Cache-Control extension | 70 * This setting is overridden by the stale-if-error HTTP Cache-Control extension |
71 * (see RFC 5861). | 71 * (see RFC 5861). |
72 * | |
73 * @param HttpKernelInterface $kernel An HttpKernelInterface instance | |
74 * @param StoreInterface $store A Store instance | |
75 * @param SurrogateInterface $surrogate A SurrogateInterface instance | |
76 * @param array $options An array of options | |
77 */ | 72 */ |
78 public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = array()) | 73 public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = array()) |
79 { | 74 { |
80 $this->store = $store; | 75 $this->store = $store; |
81 $this->kernel = $kernel; | 76 $this->kernel = $kernel; |
174 if (null !== $this->surrogate) { | 169 if (null !== $this->surrogate) { |
175 $this->surrogateCacheStrategy = $this->surrogate->createCacheStrategy(); | 170 $this->surrogateCacheStrategy = $this->surrogate->createCacheStrategy(); |
176 } | 171 } |
177 } | 172 } |
178 | 173 |
179 $path = $request->getPathInfo(); | 174 $this->traces[$this->getTraceKey($request)] = array(); |
180 if ($qs = $request->getQueryString()) { | |
181 $path .= '?'.$qs; | |
182 } | |
183 $this->traces[$request->getMethod().' '.$path] = array(); | |
184 | 175 |
185 if (!$request->isMethodSafe(false)) { | 176 if (!$request->isMethodSafe(false)) { |
186 $response = $this->invalidate($request, $catch); | 177 $response = $this->invalidate($request, $catch); |
187 } elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) { | 178 } elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) { |
188 $response = $this->pass($request, $catch); | 179 $response = $this->pass($request, $catch); |
180 } elseif ($this->options['allow_reload'] && $request->isNoCache()) { | |
181 /* | |
182 If allow_reload is configured and the client requests "Cache-Control: no-cache", | |
183 reload the cache by fetching a fresh response and caching it (if possible). | |
184 */ | |
185 $this->record($request, 'reload'); | |
186 $response = $this->fetch($request, $catch); | |
189 } else { | 187 } else { |
190 $response = $this->lookup($request, $catch); | 188 $response = $this->lookup($request, $catch); |
191 } | 189 } |
192 | 190 |
193 $this->restoreResponseBody($request, $response); | 191 $this->restoreResponseBody($request, $response); |
194 | |
195 $response->setDate(\DateTime::createFromFormat('U', time(), new \DateTimeZone('UTC'))); | |
196 | 192 |
197 if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) { | 193 if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) { |
198 $response->headers->set('X-Symfony-Cache', $this->getLog()); | 194 $response->headers->set('X-Symfony-Cache', $this->getLog()); |
199 } | 195 } |
200 | 196 |
289 * cache entry is found but is stale, it attempts to "validate" the entry with | 285 * cache entry is found but is stale, it attempts to "validate" the entry with |
290 * the backend using conditional GET. When no matching cache entry is found, | 286 * the backend using conditional GET. When no matching cache entry is found, |
291 * it triggers "miss" processing. | 287 * it triggers "miss" processing. |
292 * | 288 * |
293 * @param Request $request A Request instance | 289 * @param Request $request A Request instance |
294 * @param bool $catch whether to process exceptions | 290 * @param bool $catch Whether to process exceptions |
295 * | 291 * |
296 * @return Response A Response instance | 292 * @return Response A Response instance |
297 * | 293 * |
298 * @throws \Exception | 294 * @throws \Exception |
299 */ | 295 */ |
300 protected function lookup(Request $request, $catch = false) | 296 protected function lookup(Request $request, $catch = false) |
301 { | 297 { |
302 // if allow_reload and no-cache Cache-Control, allow a cache reload | |
303 if ($this->options['allow_reload'] && $request->isNoCache()) { | |
304 $this->record($request, 'reload'); | |
305 | |
306 return $this->fetch($request, $catch); | |
307 } | |
308 | |
309 try { | 298 try { |
310 $entry = $this->store->lookup($request); | 299 $entry = $this->store->lookup($request); |
311 } catch (\Exception $e) { | 300 } catch (\Exception $e) { |
312 $this->record($request, 'lookup-failed'); | 301 $this->record($request, 'lookup-failed'); |
313 | 302 |
401 | 390 |
402 return $response; | 391 return $response; |
403 } | 392 } |
404 | 393 |
405 /** | 394 /** |
406 * Forwards the Request to the backend and determines whether the response should be stored. | 395 * Unconditionally fetches a fresh response from the backend and |
407 * | 396 * stores it in the cache if is cacheable. |
408 * This methods is triggered when the cache missed or a reload is required. | |
409 * | 397 * |
410 * @param Request $request A Request instance | 398 * @param Request $request A Request instance |
411 * @param bool $catch whether to process exceptions | 399 * @param bool $catch Whether to process exceptions |
412 * | 400 * |
413 * @return Response A Response instance | 401 * @return Response A Response instance |
414 */ | 402 */ |
415 protected function fetch(Request $request, $catch = false) | 403 protected function fetch(Request $request, $catch = false) |
416 { | 404 { |
434 return $response; | 422 return $response; |
435 } | 423 } |
436 | 424 |
437 /** | 425 /** |
438 * Forwards the Request to the backend and returns the Response. | 426 * Forwards the Request to the backend and returns the Response. |
427 * | |
428 * All backend requests (cache passes, fetches, cache validations) | |
429 * run through this method. | |
439 * | 430 * |
440 * @param Request $request A Request instance | 431 * @param Request $request A Request instance |
441 * @param bool $catch Whether to catch exceptions or not | 432 * @param bool $catch Whether to catch exceptions or not |
442 * @param Response $entry A Response instance (the stale entry if present, null otherwise) | 433 * @param Response $entry A Response instance (the stale entry if present, null otherwise) |
443 * | 434 * |
462 $request->server->set('REMOTE_ADDR', '127.0.0.1'); | 453 $request->server->set('REMOTE_ADDR', '127.0.0.1'); |
463 | 454 |
464 // make sure HttpCache is a trusted proxy | 455 // make sure HttpCache is a trusted proxy |
465 if (!in_array('127.0.0.1', $trustedProxies = Request::getTrustedProxies())) { | 456 if (!in_array('127.0.0.1', $trustedProxies = Request::getTrustedProxies())) { |
466 $trustedProxies[] = '127.0.0.1'; | 457 $trustedProxies[] = '127.0.0.1'; |
467 Request::setTrustedProxies($trustedProxies, method_exists('Request', 'getTrustedHeaderSet') ? Request::getTrustedHeaderSet() : -1); | 458 Request::setTrustedProxies($trustedProxies, Request::HEADER_X_FORWARDED_ALL); |
468 } | 459 } |
469 | 460 |
470 // always a "master" request (as the real master request can be in cache) | 461 // always a "master" request (as the real master request can be in cache) |
471 $response = $this->kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, $catch); | 462 $response = $this->kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, $catch); |
472 // FIXME: we probably need to also catch exceptions if raw === true | 463 // FIXME: we probably need to also catch exceptions if raw === true |
482 | 473 |
483 return $entry; | 474 return $entry; |
484 } | 475 } |
485 } | 476 } |
486 | 477 |
478 /* | |
479 RFC 7231 Sect. 7.1.1.2 says that a server that does not have a reasonably accurate | |
480 clock MUST NOT send a "Date" header, although it MUST send one in most other cases | |
481 except for 1xx or 5xx responses where it MAY do so. | |
482 | |
483 Anyway, a client that received a message without a "Date" header MUST add it. | |
484 */ | |
485 if (!$response->headers->has('Date')) { | |
486 $response->setDate(\DateTime::createFromFormat('U', time())); | |
487 } | |
488 | |
487 $this->processResponseBody($request, $response); | 489 $this->processResponseBody($request, $response); |
488 | 490 |
489 if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) { | 491 if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) { |
490 $response->setPrivate(); | 492 $response->setPrivate(); |
491 } elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControlDirective('must-revalidate')) { | 493 } elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControlDirective('must-revalidate')) { |
496 } | 498 } |
497 | 499 |
498 /** | 500 /** |
499 * Checks whether the cache entry is "fresh enough" to satisfy the Request. | 501 * Checks whether the cache entry is "fresh enough" to satisfy the Request. |
500 * | 502 * |
501 * @param Request $request A Request instance | |
502 * @param Response $entry A Response instance | |
503 * | |
504 * @return bool true if the cache entry if fresh enough, false otherwise | 503 * @return bool true if the cache entry if fresh enough, false otherwise |
505 */ | 504 */ |
506 protected function isFreshEnough(Request $request, Response $entry) | 505 protected function isFreshEnough(Request $request, Response $entry) |
507 { | 506 { |
508 if (!$entry->isFresh()) { | 507 if (!$entry->isFresh()) { |
517 } | 516 } |
518 | 517 |
519 /** | 518 /** |
520 * Locks a Request during the call to the backend. | 519 * Locks a Request during the call to the backend. |
521 * | 520 * |
522 * @param Request $request A Request instance | |
523 * @param Response $entry A Response instance | |
524 * | |
525 * @return bool true if the cache entry can be returned even if it is staled, false otherwise | 521 * @return bool true if the cache entry can be returned even if it is staled, false otherwise |
526 */ | 522 */ |
527 protected function lock(Request $request, Response $entry) | 523 protected function lock(Request $request, Response $entry) |
528 { | 524 { |
529 // try to acquire a lock to call the backend | 525 // try to acquire a lock to call the backend |
530 $lock = $this->store->lock($request); | 526 $lock = $this->store->lock($request); |
531 | 527 |
528 if (true === $lock) { | |
529 // we have the lock, call the backend | |
530 return false; | |
531 } | |
532 | |
532 // there is already another process calling the backend | 533 // there is already another process calling the backend |
533 if (true !== $lock) { | 534 |
534 // check if we can serve the stale entry | 535 // May we serve a stale response? |
535 if (null === $age = $entry->headers->getCacheControlDirective('stale-while-revalidate')) { | 536 if ($this->mayServeStaleWhileRevalidate($entry)) { |
536 $age = $this->options['stale_while_revalidate']; | 537 $this->record($request, 'stale-while-revalidate'); |
537 } | |
538 | |
539 if (abs($entry->getTtl()) < $age) { | |
540 $this->record($request, 'stale-while-revalidate'); | |
541 | |
542 // server the stale response while there is a revalidation | |
543 return true; | |
544 } | |
545 | |
546 // wait for the lock to be released | |
547 $wait = 0; | |
548 while ($this->store->isLocked($request) && $wait < 5000000) { | |
549 usleep(50000); | |
550 $wait += 50000; | |
551 } | |
552 | |
553 if ($wait < 5000000) { | |
554 // replace the current entry with the fresh one | |
555 $new = $this->lookup($request); | |
556 $entry->headers = $new->headers; | |
557 $entry->setContent($new->getContent()); | |
558 $entry->setStatusCode($new->getStatusCode()); | |
559 $entry->setProtocolVersion($new->getProtocolVersion()); | |
560 foreach ($new->headers->getCookies() as $cookie) { | |
561 $entry->headers->setCookie($cookie); | |
562 } | |
563 } else { | |
564 // backend is slow as hell, send a 503 response (to avoid the dog pile effect) | |
565 $entry->setStatusCode(503); | |
566 $entry->setContent('503 Service Unavailable'); | |
567 $entry->headers->set('Retry-After', 10); | |
568 } | |
569 | 538 |
570 return true; | 539 return true; |
571 } | 540 } |
572 | 541 |
573 // we have the lock, call the backend | 542 // wait for the lock to be released |
574 return false; | 543 if ($this->waitForLock($request)) { |
544 // replace the current entry with the fresh one | |
545 $new = $this->lookup($request); | |
546 $entry->headers = $new->headers; | |
547 $entry->setContent($new->getContent()); | |
548 $entry->setStatusCode($new->getStatusCode()); | |
549 $entry->setProtocolVersion($new->getProtocolVersion()); | |
550 foreach ($new->headers->getCookies() as $cookie) { | |
551 $entry->headers->setCookie($cookie); | |
552 } | |
553 } else { | |
554 // backend is slow as hell, send a 503 response (to avoid the dog pile effect) | |
555 $entry->setStatusCode(503); | |
556 $entry->setContent('503 Service Unavailable'); | |
557 $entry->headers->set('Retry-After', 10); | |
558 } | |
559 | |
560 return true; | |
575 } | 561 } |
576 | 562 |
577 /** | 563 /** |
578 * Writes the Response to the cache. | 564 * Writes the Response to the cache. |
579 * | 565 * |
580 * @param Request $request A Request instance | |
581 * @param Response $response A Response instance | |
582 * | |
583 * @throws \Exception | 566 * @throws \Exception |
584 */ | 567 */ |
585 protected function store(Request $request, Response $response) | 568 protected function store(Request $request, Response $response) |
586 { | 569 { |
587 if (!$response->headers->has('Date')) { | |
588 $response->setDate(\DateTime::createFromFormat('U', time())); | |
589 } | |
590 try { | 570 try { |
591 $this->store->write($request, $response); | 571 $this->store->write($request, $response); |
592 | 572 |
593 $this->record($request, 'store'); | 573 $this->record($request, 'store'); |
594 | 574 |
605 $this->store->unlock($request); | 585 $this->store->unlock($request); |
606 } | 586 } |
607 | 587 |
608 /** | 588 /** |
609 * Restores the Response body. | 589 * Restores the Response body. |
610 * | |
611 * @param Request $request A Request instance | |
612 * @param Response $response A Response instance | |
613 */ | 590 */ |
614 private function restoreResponseBody(Request $request, Response $response) | 591 private function restoreResponseBody(Request $request, Response $response) |
615 { | 592 { |
616 if ($request->isMethod('HEAD') || 304 === $response->getStatusCode()) { | |
617 $response->setContent(null); | |
618 $response->headers->remove('X-Body-Eval'); | |
619 $response->headers->remove('X-Body-File'); | |
620 | |
621 return; | |
622 } | |
623 | |
624 if ($response->headers->has('X-Body-Eval')) { | 593 if ($response->headers->has('X-Body-Eval')) { |
625 ob_start(); | 594 ob_start(); |
626 | 595 |
627 if ($response->headers->has('X-Body-File')) { | 596 if ($response->headers->has('X-Body-File')) { |
628 include $response->headers->get('X-Body-File'); | 597 include $response->headers->get('X-Body-File'); |
634 $response->headers->remove('X-Body-Eval'); | 603 $response->headers->remove('X-Body-Eval'); |
635 if (!$response->headers->has('Transfer-Encoding')) { | 604 if (!$response->headers->has('Transfer-Encoding')) { |
636 $response->headers->set('Content-Length', strlen($response->getContent())); | 605 $response->headers->set('Content-Length', strlen($response->getContent())); |
637 } | 606 } |
638 } elseif ($response->headers->has('X-Body-File')) { | 607 } elseif ($response->headers->has('X-Body-File')) { |
639 $response->setContent(file_get_contents($response->headers->get('X-Body-File'))); | 608 // Response does not include possibly dynamic content (ESI, SSI), so we need |
609 // not handle the content for HEAD requests | |
610 if (!$request->isMethod('HEAD')) { | |
611 $response->setContent(file_get_contents($response->headers->get('X-Body-File'))); | |
612 } | |
640 } else { | 613 } else { |
641 return; | 614 return; |
642 } | 615 } |
643 | 616 |
644 $response->headers->remove('X-Body-File'); | 617 $response->headers->remove('X-Body-File'); |
652 } | 625 } |
653 | 626 |
654 /** | 627 /** |
655 * Checks if the Request includes authorization or other sensitive information | 628 * Checks if the Request includes authorization or other sensitive information |
656 * that should cause the Response to be considered private by default. | 629 * that should cause the Response to be considered private by default. |
657 * | |
658 * @param Request $request A Request instance | |
659 * | 630 * |
660 * @return bool true if the Request is private, false otherwise | 631 * @return bool true if the Request is private, false otherwise |
661 */ | 632 */ |
662 private function isPrivateRequest(Request $request) | 633 private function isPrivateRequest(Request $request) |
663 { | 634 { |
682 * @param Request $request A Request instance | 653 * @param Request $request A Request instance |
683 * @param string $event The event name | 654 * @param string $event The event name |
684 */ | 655 */ |
685 private function record(Request $request, $event) | 656 private function record(Request $request, $event) |
686 { | 657 { |
658 $this->traces[$this->getTraceKey($request)][] = $event; | |
659 } | |
660 | |
661 /** | |
662 * Calculates the key we use in the "trace" array for a given request. | |
663 * | |
664 * @param Request $request | |
665 * | |
666 * @return string | |
667 */ | |
668 private function getTraceKey(Request $request) | |
669 { | |
687 $path = $request->getPathInfo(); | 670 $path = $request->getPathInfo(); |
688 if ($qs = $request->getQueryString()) { | 671 if ($qs = $request->getQueryString()) { |
689 $path .= '?'.$qs; | 672 $path .= '?'.$qs; |
690 } | 673 } |
691 $this->traces[$request->getMethod().' '.$path][] = $event; | 674 |
675 return $request->getMethod().' '.$path; | |
676 } | |
677 | |
678 /** | |
679 * Checks whether the given (cached) response may be served as "stale" when a revalidation | |
680 * is currently in progress. | |
681 * | |
682 * @param Response $entry | |
683 * | |
684 * @return bool true when the stale response may be served, false otherwise | |
685 */ | |
686 private function mayServeStaleWhileRevalidate(Response $entry) | |
687 { | |
688 $timeout = $entry->headers->getCacheControlDirective('stale-while-revalidate'); | |
689 | |
690 if (null === $timeout) { | |
691 $timeout = $this->options['stale_while_revalidate']; | |
692 } | |
693 | |
694 return abs($entry->getTtl()) < $timeout; | |
695 } | |
696 | |
697 /** | |
698 * Waits for the store to release a locked entry. | |
699 * | |
700 * @param Request $request The request to wait for | |
701 * | |
702 * @return bool true if the lock was released before the internal timeout was hit; false if the wait timeout was exceeded | |
703 */ | |
704 private function waitForLock(Request $request) | |
705 { | |
706 $wait = 0; | |
707 while ($this->store->isLocked($request) && $wait < 100) { | |
708 usleep(50000); | |
709 ++$wait; | |
710 } | |
711 | |
712 return $wait < 100; | |
692 } | 713 } |
693 } | 714 } |