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