Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

HttpCache.php

Zuletzt modifiziert: 09.10.2024, 12:56 - Dateigröße: 24.22 KiB


 001  <?php
 002   
 003  /*
 004   * This file is part of the Symfony package.
 005   *
 006   * (c) Fabien Potencier <fabien@symfony.com>
 007   *
 008   * This code is partially based on the Rack-Cache library by Ryan Tomayko,
 009   * which is released under the MIT license.
 010   * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801)
 011   *
 012   * For the full copyright and license information, please view the LICENSE
 013   * file that was distributed with this source code.
 014   */
 015   
 016  namespace Symfony\Component\HttpKernel\HttpCache;
 017   
 018  use Symfony\Component\HttpKernel\HttpKernelInterface;
 019  use Symfony\Component\HttpKernel\TerminableInterface;
 020  use Symfony\Component\HttpFoundation\Request;
 021  use Symfony\Component\HttpFoundation\Response;
 022   
 023  /**
 024   * Cache provides HTTP caching.
 025   *
 026   * @author Fabien Potencier <fabien@symfony.com>
 027   */
 028  class HttpCache implements HttpKernelInterface, TerminableInterface
 029  {
 030      private $kernel;
 031      private $store;
 032      private $request;
 033      private $surrogate;
 034      private $surrogateCacheStrategy;
 035      private $options = array();
 036      private $traces = array();
 037   
 038      /**
 039       * Constructor.
 040       *
 041       * The available options are:
 042       *
 043       *   * debug:                 If true, the traces are added as a HTTP header to ease debugging
 044       *
 045       *   * default_ttl            The number of seconds that a cache entry should be considered
 046       *                            fresh when no explicit freshness information is provided in
 047       *                            a response. Explicit Cache-Control or Expires headers
 048       *                            override this value. (default: 0)
 049       *
 050       *   * private_headers        Set of request headers that trigger "private" cache-control behavior
 051       *                            on responses that don't explicitly state whether the response is
 052       *                            public or private via a Cache-Control directive. (default: Authorization and Cookie)
 053       *
 054       *   * allow_reload           Specifies whether the client can force a cache reload by including a
 055       *                            Cache-Control "no-cache" directive in the request. Set it to ``true``
 056       *                            for compliance with RFC 2616. (default: false)
 057       *
 058       *   * allow_revalidate       Specifies whether the client can force a cache revalidate by including
 059       *                            a Cache-Control "max-age=0" directive in the request. Set it to ``true``
 060       *                            for compliance with RFC 2616. (default: false)
 061       *
 062       *   * stale_while_revalidate Specifies the default number of seconds (the granularity is the second as the
 063       *                            Response TTL precision is a second) during which the cache can immediately return
 064       *                            a stale response while it revalidates it in the background (default: 2).
 065       *                            This setting is overridden by the stale-while-revalidate HTTP Cache-Control
 066       *                            extension (see RFC 5861).
 067       *
 068       *   * stale_if_error         Specifies the default number of seconds (the granularity is the second) during which
 069       *                            the cache can serve a stale response when an error is encountered (default: 60).
 070       *                            This setting is overridden by the stale-if-error HTTP Cache-Control extension
 071       *                            (see RFC 5861).
 072       *
 073       * @param HttpKernelInterface $kernel    An HttpKernelInterface instance
 074       * @param StoreInterface      $store     A Store instance
 075       * @param SurrogateInterface  $surrogate A SurrogateInterface instance
 076       * @param array               $options   An array of options
 077       */
 078      public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = array())
 079      {
 080          $this->store = $store;
 081          $this->kernel = $kernel;
 082          $this->surrogate = $surrogate;
 083   
 084          // needed in case there is a fatal error because the backend is too slow to respond
 085          register_shutdown_function(array($this->store, 'cleanup'));
 086   
 087          $this->options = array_merge(array(
 088              'debug' => false,
 089              'default_ttl' => 0,
 090              'private_headers' => array('Authorization', 'Cookie'),
 091              'allow_reload' => false,
 092              'allow_revalidate' => false,
 093              'stale_while_revalidate' => 2,
 094              'stale_if_error' => 60,
 095          ), $options);
 096      }
 097   
 098      /**
 099       * Gets the current store.
 100       *
 101       * @return StoreInterface $store A StoreInterface instance
 102       */
 103      public function getStore()
 104      {
 105          return $this->store;
 106      }
 107   
 108      /**
 109       * Returns an array of events that took place during processing of the last request.
 110       *
 111       * @return array An array of events
 112       */
 113      public function getTraces()
 114      {
 115          return $this->traces;
 116      }
 117   
 118      /**
 119       * Returns a log message for the events of the last request processing.
 120       *
 121       * @return string A log message
 122       */
 123      public function getLog()
 124      {
 125          $log = array();
 126          foreach ($this->traces as $request => $traces) {
 127              $log[] = sprintf('%s: %s', $request, implode(', ', $traces));
 128          }
 129   
 130          return implode('; ', $log);
 131      }
 132   
 133      /**
 134       * Gets the Request instance associated with the master request.
 135       *
 136       * @return Request A Request instance
 137       */
 138      public function getRequest()
 139      {
 140          return $this->request;
 141      }
 142   
 143      /**
 144       * Gets the Kernel instance.
 145       *
 146       * @return HttpKernelInterface An HttpKernelInterface instance
 147       */
 148      public function getKernel()
 149      {
 150          return $this->kernel;
 151      }
 152   
 153      /**
 154       * Gets the Surrogate instance.
 155       *
 156       * @return SurrogateInterface A Surrogate instance
 157       *
 158       * @throws \LogicException
 159       */
 160      public function getSurrogate()
 161      {
 162          if (!$this->surrogate instanceof Esi) {
 163              throw new \LogicException('This instance of HttpCache was not set up to use ESI as surrogate handler. You must overwrite and use createSurrogate');
 164          }
 165   
 166          return $this->surrogate;
 167      }
 168   
 169      /**
 170       * Gets the Esi instance.
 171       *
 172       * @return Esi An Esi instance
 173       *
 174       * @throws \LogicException
 175       *
 176       * @deprecated since version 2.6, to be removed in 3.0. Use getSurrogate() instead
 177       */
 178      public function getEsi()
 179      {
 180          @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the getSurrogate() method instead.', E_USER_DEPRECATED);
 181   
 182          return $this->getSurrogate();
 183      }
 184   
 185      /**
 186       * {@inheritdoc}
 187       */
 188      public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
 189      {
 190          // FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism
 191          if (HttpKernelInterface::MASTER_REQUEST === $type) {
 192              $this->traces = array();
 193              $this->request = $request;
 194              if (null !== $this->surrogate) {
 195                  $this->surrogateCacheStrategy = $this->surrogate->createCacheStrategy();
 196              }
 197          }
 198   
 199          $path = $request->getPathInfo();
 200          if ($qs = $request->getQueryString()) {
 201              $path .= '?'.$qs;
 202          }
 203          $this->traces[$request->getMethod().' '.$path] = array();
 204   
 205          if (!$request->isMethodSafe()) {
 206              $response = $this->invalidate($request, $catch);
 207          } elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) {
 208              $response = $this->pass($request, $catch);
 209          } else {
 210              $response = $this->lookup($request, $catch);
 211          }
 212   
 213          $this->restoreResponseBody($request, $response);
 214   
 215          $response->setDate(\DateTime::createFromFormat('U', time(), new \DateTimeZone('UTC')));
 216   
 217          if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) {
 218              $response->headers->set('X-Symfony-Cache', $this->getLog());
 219          }
 220   
 221          if (null !== $this->surrogate) {
 222              if (HttpKernelInterface::MASTER_REQUEST === $type) {
 223                  $this->surrogateCacheStrategy->update($response);
 224              } else {
 225                  $this->surrogateCacheStrategy->add($response);
 226              }
 227          }
 228   
 229          $response->prepare($request);
 230   
 231          $response->isNotModified($request);
 232   
 233          return $response;
 234      }
 235   
 236      /**
 237       * {@inheritdoc}
 238       */
 239      public function terminate(Request $request, Response $response)
 240      {
 241          if ($this->getKernel() instanceof TerminableInterface) {
 242              $this->getKernel()->terminate($request, $response);
 243          }
 244      }
 245   
 246      /**
 247       * Forwards the Request to the backend without storing the Response in the cache.
 248       *
 249       * @param Request $request A Request instance
 250       * @param bool    $catch   Whether to process exceptions
 251       *
 252       * @return Response A Response instance
 253       */
 254      protected function pass(Request $request, $catch = false)
 255      {
 256          $this->record($request, 'pass');
 257   
 258          return $this->forward($request, $catch);
 259      }
 260   
 261      /**
 262       * Invalidates non-safe methods (like POST, PUT, and DELETE).
 263       *
 264       * @param Request $request A Request instance
 265       * @param bool    $catch   Whether to process exceptions
 266       *
 267       * @return Response A Response instance
 268       *
 269       * @throws \Exception
 270       *
 271       * @see RFC2616 13.10
 272       */
 273      protected function invalidate(Request $request, $catch = false)
 274      {
 275          $response = $this->pass($request, $catch);
 276   
 277          // invalidate only when the response is successful
 278          if ($response->isSuccessful() || $response->isRedirect()) {
 279              try {
 280                  $this->store->invalidate($request);
 281   
 282                  // As per the RFC, invalidate Location and Content-Location URLs if present
 283                  foreach (array('Location', 'Content-Location') as $header) {
 284                      if ($uri = $response->headers->get($header)) {
 285                          $subRequest = Request::create($uri, 'get', array(), array(), array(), $request->server->all());
 286   
 287                          $this->store->invalidate($subRequest);
 288                      }
 289                  }
 290   
 291                  $this->record($request, 'invalidate');
 292              } catch (\Exception $e) {
 293                  $this->record($request, 'invalidate-failed');
 294   
 295                  if ($this->options['debug']) {
 296                      throw $e;
 297                  }
 298              }
 299          }
 300   
 301          return $response;
 302      }
 303   
 304      /**
 305       * Lookups a Response from the cache for the given Request.
 306       *
 307       * When a matching cache entry is found and is fresh, it uses it as the
 308       * response without forwarding any request to the backend. When a matching
 309       * cache entry is found but is stale, it attempts to "validate" the entry with
 310       * the backend using conditional GET. When no matching cache entry is found,
 311       * it triggers "miss" processing.
 312       *
 313       * @param Request $request A Request instance
 314       * @param bool    $catch   whether to process exceptions
 315       *
 316       * @return Response A Response instance
 317       *
 318       * @throws \Exception
 319       */
 320      protected function lookup(Request $request, $catch = false)
 321      {
 322          // if allow_reload and no-cache Cache-Control, allow a cache reload
 323          if ($this->options['allow_reload'] && $request->isNoCache()) {
 324              $this->record($request, 'reload');
 325   
 326              return $this->fetch($request, $catch);
 327          }
 328   
 329          try {
 330              $entry = $this->store->lookup($request);
 331          } catch (\Exception $e) {
 332              $this->record($request, 'lookup-failed');
 333   
 334              if ($this->options['debug']) {
 335                  throw $e;
 336              }
 337   
 338              return $this->pass($request, $catch);
 339          }
 340   
 341          if (null === $entry) {
 342              $this->record($request, 'miss');
 343   
 344              return $this->fetch($request, $catch);
 345          }
 346   
 347          if (!$this->isFreshEnough($request, $entry)) {
 348              $this->record($request, 'stale');
 349   
 350              return $this->validate($request, $entry, $catch);
 351          }
 352   
 353          $this->record($request, 'fresh');
 354   
 355          $entry->headers->set('Age', $entry->getAge());
 356   
 357          return $entry;
 358      }
 359   
 360      /**
 361       * Validates that a cache entry is fresh.
 362       *
 363       * The original request is used as a template for a conditional
 364       * GET request with the backend.
 365       *
 366       * @param Request  $request A Request instance
 367       * @param Response $entry   A Response instance to validate
 368       * @param bool     $catch   Whether to process exceptions
 369       *
 370       * @return Response A Response instance
 371       */
 372      protected function validate(Request $request, Response $entry, $catch = false)
 373      {
 374          $subRequest = clone $request;
 375   
 376          // send no head requests because we want content
 377          if ('HEAD' === $request->getMethod()) {
 378              $subRequest->setMethod('GET');
 379          }
 380   
 381          // add our cached last-modified validator
 382          $subRequest->headers->set('if_modified_since', $entry->headers->get('Last-Modified'));
 383   
 384          // Add our cached etag validator to the environment.
 385          // We keep the etags from the client to handle the case when the client
 386          // has a different private valid entry which is not cached here.
 387          $cachedEtags = $entry->getEtag() ? array($entry->getEtag()) : array();
 388          $requestEtags = $request->getETags();
 389          if ($etags = array_unique(array_merge($cachedEtags, $requestEtags))) {
 390              $subRequest->headers->set('if_none_match', implode(', ', $etags));
 391          }
 392   
 393          $response = $this->forward($subRequest, $catch, $entry);
 394   
 395          if (304 == $response->getStatusCode()) {
 396              $this->record($request, 'valid');
 397   
 398              // return the response and not the cache entry if the response is valid but not cached
 399              $etag = $response->getEtag();
 400              if ($etag && in_array($etag, $requestEtags) && !in_array($etag, $cachedEtags)) {
 401                  return $response;
 402              }
 403   
 404              $entry = clone $entry;
 405              $entry->headers->remove('Date');
 406   
 407              foreach (array('Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified') as $name) {
 408                  if ($response->headers->has($name)) {
 409                      $entry->headers->set($name, $response->headers->get($name));
 410                  }
 411              }
 412   
 413              $response = $entry;
 414          } else {
 415              $this->record($request, 'invalid');
 416          }
 417   
 418          if ($response->isCacheable()) {
 419              $this->store($request, $response);
 420          }
 421   
 422          return $response;
 423      }
 424   
 425      /**
 426       * Forwards the Request to the backend and determines whether the response should be stored.
 427       *
 428       * This methods is triggered when the cache missed or a reload is required.
 429       *
 430       * @param Request $request A Request instance
 431       * @param bool    $catch   whether to process exceptions
 432       *
 433       * @return Response A Response instance
 434       */
 435      protected function fetch(Request $request, $catch = false)
 436      {
 437          $subRequest = clone $request;
 438   
 439          // send no head requests because we want content
 440          if ('HEAD' === $request->getMethod()) {
 441              $subRequest->setMethod('GET');
 442          }
 443   
 444          // avoid that the backend sends no content
 445          $subRequest->headers->remove('if_modified_since');
 446          $subRequest->headers->remove('if_none_match');
 447   
 448          $response = $this->forward($subRequest, $catch);
 449   
 450          if ($response->isCacheable()) {
 451              $this->store($request, $response);
 452          }
 453   
 454          return $response;
 455      }
 456   
 457      /**
 458       * Forwards the Request to the backend and returns the Response.
 459       *
 460       * @param Request  $request A Request instance
 461       * @param bool     $catch   Whether to catch exceptions or not
 462       * @param Response $entry   A Response instance (the stale entry if present, null otherwise)
 463       *
 464       * @return Response A Response instance
 465       */
 466      protected function forward(Request $request, $catch = false, Response $entry = null)
 467      {
 468          if ($this->surrogate) {
 469              $this->surrogate->addSurrogateCapability($request);
 470          }
 471   
 472          // modify the X-Forwarded-For header if needed
 473          $forwardedFor = $request->headers->get('X-Forwarded-For');
 474          if ($forwardedFor) {
 475              $request->headers->set('X-Forwarded-For', $forwardedFor.', '.$request->server->get('REMOTE_ADDR'));
 476          } else {
 477              $request->headers->set('X-Forwarded-For', $request->server->get('REMOTE_ADDR'));
 478          }
 479   
 480          // fix the client IP address by setting it to 127.0.0.1 as HttpCache
 481          // is always called from the same process as the backend.
 482          $request->server->set('REMOTE_ADDR', '127.0.0.1');
 483   
 484          // make sure HttpCache is a trusted proxy
 485          if (!in_array('127.0.0.1', $trustedProxies = Request::getTrustedProxies())) {
 486              $trustedProxies[] = '127.0.0.1';
 487              Request::setTrustedProxies($trustedProxies);
 488          }
 489   
 490          // always a "master" request (as the real master request can be in cache)
 491          $response = $this->kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, $catch);
 492          // FIXME: we probably need to also catch exceptions if raw === true
 493   
 494          // we don't implement the stale-if-error on Requests, which is nonetheless part of the RFC
 495          if (null !== $entry && in_array($response->getStatusCode(), array(500, 502, 503, 504))) {
 496              if (null === $age = $entry->headers->getCacheControlDirective('stale-if-error')) {
 497                  $age = $this->options['stale_if_error'];
 498              }
 499   
 500              if (abs($entry->getTtl()) < $age) {
 501                  $this->record($request, 'stale-if-error');
 502   
 503                  return $entry;
 504              }
 505          }
 506   
 507          $this->processResponseBody($request, $response);
 508   
 509          if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) {
 510              $response->setPrivate();
 511          } elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControlDirective('must-revalidate')) {
 512              $response->setTtl($this->options['default_ttl']);
 513          }
 514   
 515          return $response;
 516      }
 517   
 518      /**
 519       * Checks whether the cache entry is "fresh enough" to satisfy the Request.
 520       *
 521       * @param Request  $request A Request instance
 522       * @param Response $entry   A Response instance
 523       *
 524       * @return bool true if the cache entry if fresh enough, false otherwise
 525       */
 526      protected function isFreshEnough(Request $request, Response $entry)
 527      {
 528          if (!$entry->isFresh()) {
 529              return $this->lock($request, $entry);
 530          }
 531   
 532          if ($this->options['allow_revalidate'] && null !== $maxAge = $request->headers->getCacheControlDirective('max-age')) {
 533              return $maxAge > 0 && $maxAge >= $entry->getAge();
 534          }
 535   
 536          return true;
 537      }
 538   
 539      /**
 540       * Locks a Request during the call to the backend.
 541       *
 542       * @param Request  $request A Request instance
 543       * @param Response $entry   A Response instance
 544       *
 545       * @return bool true if the cache entry can be returned even if it is staled, false otherwise
 546       */
 547      protected function lock(Request $request, Response $entry)
 548      {
 549          // try to acquire a lock to call the backend
 550          $lock = $this->store->lock($request);
 551   
 552          // there is already another process calling the backend
 553          if (true !== $lock) {
 554              // check if we can serve the stale entry
 555              if (null === $age = $entry->headers->getCacheControlDirective('stale-while-revalidate')) {
 556                  $age = $this->options['stale_while_revalidate'];
 557              }
 558   
 559              if (abs($entry->getTtl()) < $age) {
 560                  $this->record($request, 'stale-while-revalidate');
 561   
 562                  // server the stale response while there is a revalidation
 563                  return true;
 564              }
 565   
 566              // wait for the lock to be released
 567              $wait = 0;
 568              while ($this->store->isLocked($request) && $wait < 5000000) {
 569                  usleep(50000);
 570                  $wait += 50000;
 571              }
 572   
 573              if ($wait < 5000000) {
 574                  // replace the current entry with the fresh one
 575                  $new = $this->lookup($request);
 576                  $entry->headers = $new->headers;
 577                  $entry->setContent($new->getContent());
 578                  $entry->setStatusCode($new->getStatusCode());
 579                  $entry->setProtocolVersion($new->getProtocolVersion());
 580                  foreach ($new->headers->getCookies() as $cookie) {
 581                      $entry->headers->setCookie($cookie);
 582                  }
 583              } else {
 584                  // backend is slow as hell, send a 503 response (to avoid the dog pile effect)
 585                  $entry->setStatusCode(503);
 586                  $entry->setContent('503 Service Unavailable');
 587                  $entry->headers->set('Retry-After', 10);
 588              }
 589   
 590              return true;
 591          }
 592   
 593          // we have the lock, call the backend
 594          return false;
 595      }
 596   
 597      /**
 598       * Writes the Response to the cache.
 599       *
 600       * @param Request  $request  A Request instance
 601       * @param Response $response A Response instance
 602       *
 603       * @throws \Exception
 604       */
 605      protected function store(Request $request, Response $response)
 606      {
 607          if (!$response->headers->has('Date')) {
 608              $response->setDate(\DateTime::createFromFormat('U', time()));
 609          }
 610          try {
 611              $this->store->write($request, $response);
 612   
 613              $this->record($request, 'store');
 614   
 615              $response->headers->set('Age', $response->getAge());
 616          } catch (\Exception $e) {
 617              $this->record($request, 'store-failed');
 618   
 619              if ($this->options['debug']) {
 620                  throw $e;
 621              }
 622          }
 623   
 624          // now that the response is cached, release the lock
 625          $this->store->unlock($request);
 626      }
 627   
 628      /**
 629       * Restores the Response body.
 630       *
 631       * @param Request  $request  A Request instance
 632       * @param Response $response A Response instance
 633       */
 634      private function restoreResponseBody(Request $request, Response $response)
 635      {
 636          if ($request->isMethod('HEAD') || 304 === $response->getStatusCode()) {
 637              $response->setContent(null);
 638              $response->headers->remove('X-Body-Eval');
 639              $response->headers->remove('X-Body-File');
 640   
 641              return;
 642          }
 643   
 644          if ($response->headers->has('X-Body-Eval')) {
 645              ob_start();
 646   
 647              if ($response->headers->has('X-Body-File')) {
 648                  include $response->headers->get('X-Body-File');
 649              } else {
 650                  eval('; ?>'.$response->getContent().'<?php ;');
 651              }
 652   
 653              $response->setContent(ob_get_clean());
 654              $response->headers->remove('X-Body-Eval');
 655              if (!$response->headers->has('Transfer-Encoding')) {
 656                  $response->headers->set('Content-Length', strlen($response->getContent()));
 657              }
 658          } elseif ($response->headers->has('X-Body-File')) {
 659              $response->setContent(file_get_contents($response->headers->get('X-Body-File')));
 660          } else {
 661              return;
 662          }
 663   
 664          $response->headers->remove('X-Body-File');
 665      }
 666   
 667      protected function processResponseBody(Request $request, Response $response)
 668      {
 669          if (null !== $this->surrogate && $this->surrogate->needsParsing($response)) {
 670              $this->surrogate->process($request, $response);
 671          }
 672      }
 673   
 674      /**
 675       * Checks if the Request includes authorization or other sensitive information
 676       * that should cause the Response to be considered private by default.
 677       *
 678       * @param Request $request A Request instance
 679       *
 680       * @return bool true if the Request is private, false otherwise
 681       */
 682      private function isPrivateRequest(Request $request)
 683      {
 684          foreach ($this->options['private_headers'] as $key) {
 685              $key = strtolower(str_replace('HTTP_', '', $key));
 686   
 687              if ('cookie' === $key) {
 688                  if (count($request->cookies->all())) {
 689                      return true;
 690                  }
 691              } elseif ($request->headers->has($key)) {
 692                  return true;
 693              }
 694          }
 695   
 696          return false;
 697      }
 698   
 699      /**
 700       * Records that an event took place.
 701       *
 702       * @param Request $request A Request instance
 703       * @param string  $event   The event name
 704       */
 705      private function record(Request $request, $event)
 706      {
 707          $path = $request->getPathInfo();
 708          if ($qs = $request->getQueryString()) {
 709              $path .= '?'.$qs;
 710          }
 711          $this->traces[$request->getMethod().' '.$path][] = $event;
 712      }
 713  }
 714