/**
  * @param ResponseInterface $response
  * @return CacheEntry|null entry to save, null if can't cache it
  */
 protected function getCacheObject(ResponseInterface $response)
 {
     if ($response->hasHeader("Cache-Control")) {
         $values = new KeyValueHttpHeader($response->getHeader("Cache-Control"));
         if ($values->has('no-store')) {
             // No store allowed (maybe some sensitives data...)
             return null;
         }
         if ($values->has('no-cache')) {
             // Stale response see RFC7234 section 5.2.1.4
             $entry = new CacheEntry($response, new \DateTime('-1 seconds'));
             return $entry->hasValidationInformation() ? $entry : null;
         }
         if ($values->has('max-age')) {
             return new CacheEntry($response, new \DateTime('+' . $values->get('max-age') . 'seconds'));
         }
         return new CacheEntry($response, new \DateTime());
     }
     if ($response->hasHeader("Expires")) {
         $expireAt = \DateTime::createFromFormat('D, d M Y H:i:s T', $response->getHeaderLine("Expires"));
         if ($expireAt !== FALSE) {
             return new CacheEntry($response, $expireAt);
         }
     }
     return new CacheEntry($response, new \DateTime('-1 seconds'));
 }
 /**
  * {@inheritdoc}
  */
 protected function getCacheObject(RequestInterface $request, ResponseInterface $response)
 {
     $cacheControl = new KeyValueHttpHeader($response->getHeader('Cache-Control'));
     if ($cacheControl->has('private')) {
         return;
     }
     return parent::getCacheObject($request, $response);
 }
 /**
  * @param ResponseInterface $response
  * @param \DateTime $staleAt
  * @param \DateTime|null $staleIfErrorTo if null, detected with the headers (RFC 5861)
  * @param \DateTime $staleWhileRevalidateTo
  */
 public function __construct(ResponseInterface $response, \DateTime $staleAt, \DateTime $staleIfErrorTo = null, \DateTime $staleWhileRevalidateTo = null)
 {
     $this->response = $response;
     $this->staleAt = $staleAt;
     $values = new KeyValueHttpHeader($response->getHeader("Cache-Control"));
     if ($staleIfErrorTo === null && $values->has('stale-if-error')) {
         $this->staleIfErrorTo = (new \DateTime())->setTimestamp($this->staleAt->getTimestamp() + (int) $values->get('stale-if-error'));
     } else {
         $this->staleIfErrorTo = $staleIfErrorTo;
     }
     if ($staleWhileRevalidateTo === null && $values->has('stale-while-revalidate')) {
         $this->staleWhileRevalidateTo = new \DateTime('+' . $values->get('stale-while-revalidate') . 'seconds');
     } else {
         $this->staleWhileRevalidateTo = $staleWhileRevalidateTo;
     }
 }
 /**
  * @param ResponseInterface $response
  * @param KeyValueHttpHeader $cacheControl
  * @return CacheEntry|null
  */
 protected function getCacheObjectForCacheControl(ResponseInterface $response, KeyValueHttpHeader $cacheControl)
 {
     if ($cacheControl->has('no-store')) {
         // No store allowed (maybe some sensitives data...)
         return null;
     }
     if ($cacheControl->has('no-cache')) {
         // Stale response see RFC7234 section 5.2.1.4
         $entry = new CacheEntry($response, new \DateTime('-1 seconds'));
         return $entry->hasValidationInformation() ? $entry : null;
     }
     if ($cacheControl->has('max-age')) {
         return new CacheEntry($response, new \DateTime('+' . (int) $cacheControl->get('max-age') . 'seconds'));
     }
     return new CacheEntry($response, new \DateTime());
 }
 /**
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @param \DateTime $staleAt
  * @param \DateTime|null $staleIfErrorTo if null, detected with the headers (RFC 5861)
  * @param \DateTime|null $staleWhileRevalidateTo
  */
 public function __construct(RequestInterface $request, ResponseInterface $response, \DateTime $staleAt, \DateTime $staleIfErrorTo = null, \DateTime $staleWhileRevalidateTo = null)
 {
     $this->dateCreated = new \DateTime();
     $this->request = $request;
     $this->response = $response;
     $this->staleAt = $staleAt;
     $values = new KeyValueHttpHeader($response->getHeader('Cache-Control'));
     if ($staleIfErrorTo === null && $values->has('stale-if-error')) {
         $this->staleIfErrorTo = new \DateTime('@' . ($this->staleAt->getTimestamp() + (int) $values->get('stale-if-error')));
     } else {
         $this->staleIfErrorTo = $staleIfErrorTo;
     }
     if ($staleWhileRevalidateTo === null && $values->has('stale-while-revalidate')) {
         $this->staleWhileRevalidateTo = new \DateTime('@' . ($this->staleAt->getTimestamp() + (int) $values->get('stale-while-revalidate')));
     } else {
         $this->staleWhileRevalidateTo = $staleWhileRevalidateTo;
     }
 }
 public function testBase()
 {
     $response = new Response(200, ['Cache-Control' => ['max-age=120', ' stale-while-revalidate=60 ', '  private ', 'zero=0', 'nothing=', 'false=false', 'with-comma=1,yeah="2"']]);
     $values = new KeyValueHttpHeader($response->getHeader('Cache-Control'));
     $this->assertTrue($values->has('max-age'));
     $this->assertTrue($values->has('stale-while-revalidate'));
     $this->assertTrue($values->has('private'));
     $this->assertTrue($values->has('zero'));
     $this->assertTrue($values->has('nothing'));
     $this->assertTrue($values->has('false'));
     $this->assertTrue($values->has('with-comma'));
     $this->assertTrue($values->has('yeah'));
     $this->assertEquals(120, $values->get('max-age'));
     $this->assertEquals(60, $values->get('stale-while-revalidate'));
     $this->assertEquals(0, $values->get('zero'));
     $this->assertEquals('', $values->get('nothing'));
     $this->assertEquals('false', $values->get('false'));
     $this->assertEquals(1, $values->get('with-comma'));
     $this->assertEquals(2, $values->get('yeah'));
 }
 /**
  * @param RequestInterface  $request
  * @param ResponseInterface $response
  *
  * @return bool true if success
  */
 public function cache(RequestInterface $request, ResponseInterface $response)
 {
     $reqCacheControl = new KeyValueHttpHeader($request->getHeader('Cache-Control'));
     if ($reqCacheControl->has('no-store')) {
         // No caching allowed
         return false;
     }
     $cacheObject = $this->getCacheObject($request, $response);
     if ($cacheObject !== null) {
         return $this->storage->save($this->getCacheKey($request), $cacheObject);
     }
     return false;
 }
 /**
  * @param callable $handler
  *
  * @return callable
  */
 public function __invoke(callable $handler)
 {
     return function (RequestInterface $request, array $options) use(&$handler) {
         if (!isset($this->httpMethods[strtoupper($request->getMethod())])) {
             // No caching for this method allowed
             return $handler($request, $options)->then(function (ResponseInterface $response) {
                 return $response->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_MISS);
             });
         }
         if ($request->hasHeader(self::HEADER_RE_VALIDATION)) {
             // It's a re-validation request, so bypass the cache!
             return $handler($request->withoutHeader(self::HEADER_RE_VALIDATION), $options);
         }
         // Retrieve information from request (Cache-Control)
         $reqCacheControl = new KeyValueHttpHeader($request->getHeader('Cache-Control'));
         $onlyFromCache = $reqCacheControl->has('only-if-cached');
         $staleResponse = $reqCacheControl->has('max-stale') && $reqCacheControl->get('max-stale') === '';
         $maxStaleCache = $reqCacheControl->get('max-stale', null);
         $minFreshCache = $reqCacheControl->get('min-fresh', null);
         // If cache => return new FulfilledPromise(...) with response
         $cacheEntry = $this->cacheStorage->fetch($request);
         if ($cacheEntry instanceof CacheEntry) {
             if ($cacheEntry->isFresh() && ($minFreshCache === null || $cacheEntry->getStaleAge() + (int) $minFreshCache <= 0)) {
                 // Cache HIT!
                 return new FulfilledPromise($cacheEntry->getResponse()->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_HIT));
             } elseif ($staleResponse || $maxStaleCache !== null && $cacheEntry->getStaleAge() <= $maxStaleCache) {
                 // Staled cache!
                 return new FulfilledPromise($cacheEntry->getResponse()->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_HIT));
             } elseif ($cacheEntry->hasValidationInformation() && !$onlyFromCache) {
                 // Re-validation header
                 $request = static::getRequestWithReValidationHeader($request, $cacheEntry);
                 if ($cacheEntry->staleWhileValidate()) {
                     static::addReValidationRequest($request, $this->cacheStorage, $cacheEntry);
                     return new FulfilledPromise($cacheEntry->getResponse()->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_STALE));
                 }
             }
         } else {
             $cacheEntry = null;
         }
         if ($cacheEntry === null && $onlyFromCache) {
             // Explicit asking of a cached response => 504
             return new FulfilledPromise(new Response(504));
         }
         /** @var Promise $promise */
         $promise = $handler($request, $options);
         return $promise->then(function (ResponseInterface $response) use($request, $cacheEntry) {
             // Check if error and looking for a staled content
             if ($response->getStatusCode() >= 500) {
                 $responseStale = static::getStaleResponse($cacheEntry);
                 if ($responseStale instanceof ResponseInterface) {
                     return $responseStale;
                 }
             }
             if ($response->getStatusCode() == 304 && $cacheEntry instanceof CacheEntry) {
                 // Not modified => cache entry is re-validate
                 /** @var ResponseInterface $response */
                 $response = $response->withStatus($cacheEntry->getResponse()->getStatusCode())->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_HIT);
                 $response = $response->withBody($cacheEntry->getResponse()->getBody());
             } else {
                 $response = $response->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_MISS);
             }
             // Add to the cache
             $this->cacheStorage->cache($request, $response);
             return $response;
         }, function (\Exception $ex) use($cacheEntry) {
             if ($ex instanceof TransferException) {
                 $response = static::getStaleResponse($cacheEntry);
                 if ($response instanceof ResponseInterface) {
                     return $response;
                 }
             }
             throw $ex;
         });
     };
 }