/** * {@inheritdoc} */ public function updateMetadata(Response $response, BlockContextInterface $blockContext = null) { if ($this->currentTtl === null) { $this->currentTtl = $response->getTtl(); } if ($response->isCacheable() !== null && $response->getTtl() < $this->currentTtl) { $this->currentTtl = $response->getTtl(); } }
/** * {@inheritdoc} */ public function update(Response $response) { // if we have no embedded Response, do nothing if (0 === $this->embeddedResponses) { return; } // Remove validation related headers in order to avoid browsers using // their own cache, because some of the response content comes from // at least one embedded response (which likely has a different caching strategy). if ($response->isValidateable()) { $response->setEtag(null); $response->setLastModified(null); $this->cacheable = false; } if (!$this->cacheable) { $response->headers->set('Cache-Control', 'no-cache, must-revalidate'); return; } $this->ttls[] = $response->getTtl(); $this->maxAges[] = $response->getMaxAge(); if (null !== ($maxAge = min($this->maxAges))) { $response->setSharedMaxAge($maxAge); $response->headers->set('Age', $maxAge - min($this->ttls)); } $response->setMaxAge(0); }
/** * @return int */ protected function getTtl() { if ($this->response === null) { return null; } return $this->response->getTtl(); }
/** * Adds a Response. * * @param Response $response */ public function add(Response $response) { if ($response->isValidateable()) { $this->cacheable = false; } else { $this->ttls[] = $response->getTtl(); $this->maxAges[] = $response->getMaxAge(); } }
protected function logResponse(Response $response, Request $request) { if ($response->getStatusCode() >= 500) { $color = LogLevel::ERROR; } elseif ($response->getStatusCode() >= 400) { $color = LogLevel::WARNING; } elseif ($response->getStatusCode() >= 300) { $color = LogLevel::NOTICE; } elseif ($response->getStatusCode() >= 200) { $color = LogLevel::INFO; } else { $color = LogLevel::INFO; } $msg = 'Response {response_status_code} for "{request_method} {request_uri}"'; $context = array('request_method' => $request->getMethod(), 'request_uri' => $request->getRequestUri(), 'response_status_code' => $response->getStatusCode(), 'response_charset' => $response->getCharset(), 'response_date' => $response->getDate(), 'response_etag' => $response->getEtag(), 'response_expires' => $response->getExpires(), 'response_last_modified' => $response->getLastModified(), 'response_max_age' => $response->getMaxAge(), 'response_protocol_version' => $response->getProtocolVersion(), 'response_ttl' => $response->getTtl(), 'response_vary' => $response->getVary()); $this->logger->log($color, $msg, $context); }
/** * This method is responsible to cascade ttl to the parent block. * * @param Response $response * @param BlockContextInterface $blockContext * @param BlockServiceInterface $service * * @return Response */ protected function addMetaInformation(Response $response, BlockContextInterface $blockContext, BlockServiceInterface $service) { // a response exists, use it if ($this->lastResponse && $this->lastResponse->isCacheable()) { $response->setTtl($this->lastResponse->getTtl()); $response->setPublic(); } elseif ($this->lastResponse) { // not cacheable $response->setPrivate(); $response->setTtl(0); $response->headers->removeCacheControlDirective('s-maxage'); $response->headers->removeCacheControlDirective('maxage'); } // no more children available in the stack, reseting the state object if (!$blockContext->getBlock()->hasParent()) { $this->lastResponse = null; } else { // contains a parent so storing the response $this->lastResponse = $response; } return $response; }
public function testGetTtl() { $response = new Response(); $this->assertNull($response->getTtl(), '->getTtl() returns null when no Expires or Cache-Control headers are present'); $response = new Response(); $response->headers->set('Expires', $this->createDateTimeOneHourLater()->format(DATE_RFC2822)); $this->assertLessThan(1, 3600 - $response->getTtl(), '->getTtl() uses the Expires header when no max-age is present'); $response = new Response(); $response->headers->set('Expires', $this->createDateTimeOneHourAgo()->format(DATE_RFC2822)); $this->assertLessThan(0, $response->getTtl(), '->getTtl() returns negative values when Expires is in past'); $response = new Response(); $response->headers->set('Expires', $response->getDate()->format(DATE_RFC2822)); $response->headers->set('Age', 0); $this->assertSame(0, $response->getTtl(), '->getTtl() correctly handles zero'); $response = new Response(); $response->headers->set('Cache-Control', 'max-age=60'); $this->assertLessThan(1, 60 - $response->getTtl(), '->getTtl() uses Cache-Control max-age when present'); }
/** * 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) { // try to acquire a lock to call the backend $lock = $this->store->lock($request); // 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'); // 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; } if ($wait < 2000000) { // 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); } return true; } // we have the lock, call the backend return false; }
/** * Forwards the Request to the backend and returns the Response. * * @param Request $request A Request instance * @param Boolean $catch Whether to catch exceptions or not * @param Response $entry A Response instance (the stale entry if present, null otherwise) * * @return Response A Response instance */ protected function forward(Request $request, $catch = false, Response $entry = null) { if ($this->esi) { $this->esi->addSurrogateEsiCapability($request); } // 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 // 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 === $age = $entry->headers->getCacheControlDirective('stale-if-error')) { $age = $this->options['stale_if_error']; } if (abs($entry->getTtl()) < $age) { $this->record($request, 'stale-if-error'); return $entry; } } $this->processResponseBody($request, $response); return $response; }
/** * Forwards the Request to the backend and returns the Response. * * @param Request $request A Request instance * @param Boolean $catch Whether to catch exceptions or not * @param Response $entry A Response instance (the stale entry if present, null otherwise) * * @return Response A Response instance */ protected function forward(Request $request, $catch = false, Response $entry = null) { if ($this->esi) { $this->esi->addSurrogateEsiCapability($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'); // 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 // 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 === ($age = $entry->headers->getCacheControlDirective('stale-if-error'))) { $age = $this->options['stale_if_error']; } if (abs($entry->getTtl()) < $age) { $this->record($request, 'stale-if-error'); return $entry; } } $this->processResponseBody($request, $response); return $response; }
/** * @param Response $response * @param Request $request * * @return array */ protected function createContext(Response $response, Request $request) { $context = array('response_status_code' => $response->getStatusCode(), 'response_charset' => $response->getCharset(), 'response_date' => $response->getDate(), 'response_etag' => $response->getEtag(), 'response_expires' => $response->getExpires(), 'response_last_modified' => $response->getLastModified(), 'response_max_age' => $response->getMaxAge(), 'response_protocol_version' => $response->getProtocolVersion(), 'response_ttl' => $response->getTtl(), 'response_vary' => $response->getVary(), 'request_method' => $request->getMethod(), 'request_uri' => $request->getRequestUri(), 'request_route' => $request->attributes->get('_route'), 'response_time' => $this->getTime($request), 'response_memory' => $this->getMemory()); return $context; }