[ Index ] |
PHP Cross Reference of phpBB-3.2.11-deutsch |
[Summary view] [Print] [Text view]
1 <?php 2 3 /* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * This code is partially based on the Rack-Cache library by Ryan Tomayko, 9 * which is released under the MIT license. 10 * 11 * For the full copyright and license information, please view the LICENSE 12 * file that was distributed with this source code. 13 */ 14 15 namespace Symfony\Component\HttpKernel\HttpCache; 16 17 use Symfony\Component\HttpFoundation\Request; 18 use Symfony\Component\HttpFoundation\Response; 19 20 /** 21 * Store implements all the logic for storing cache metadata (Request and Response headers). 22 * 23 * @author Fabien Potencier <fabien@symfony.com> 24 */ 25 class Store implements StoreInterface 26 { 27 protected $root; 28 private $keyCache; 29 private $locks; 30 31 /** 32 * @param string $root The path to the cache directory 33 * 34 * @throws \RuntimeException 35 */ 36 public function __construct($root) 37 { 38 $this->root = $root; 39 if (!file_exists($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) { 40 throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root)); 41 } 42 $this->keyCache = new \SplObjectStorage(); 43 $this->locks = array(); 44 } 45 46 /** 47 * Cleanups storage. 48 */ 49 public function cleanup() 50 { 51 // unlock everything 52 foreach ($this->locks as $lock) { 53 flock($lock, LOCK_UN); 54 fclose($lock); 55 } 56 57 $this->locks = array(); 58 } 59 60 /** 61 * Tries to lock the cache for a given Request, without blocking. 62 * 63 * @return bool|string true if the lock is acquired, the path to the current lock otherwise 64 */ 65 public function lock(Request $request) 66 { 67 $key = $this->getCacheKey($request); 68 69 if (!isset($this->locks[$key])) { 70 $path = $this->getPath($key); 71 if (!file_exists(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) { 72 return $path; 73 } 74 $h = fopen($path, 'cb'); 75 if (!flock($h, LOCK_EX | LOCK_NB)) { 76 fclose($h); 77 78 return $path; 79 } 80 81 $this->locks[$key] = $h; 82 } 83 84 return true; 85 } 86 87 /** 88 * Releases the lock for the given Request. 89 * 90 * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise 91 */ 92 public function unlock(Request $request) 93 { 94 $key = $this->getCacheKey($request); 95 96 if (isset($this->locks[$key])) { 97 flock($this->locks[$key], LOCK_UN); 98 fclose($this->locks[$key]); 99 unset($this->locks[$key]); 100 101 return true; 102 } 103 104 return false; 105 } 106 107 public function isLocked(Request $request) 108 { 109 $key = $this->getCacheKey($request); 110 111 if (isset($this->locks[$key])) { 112 return true; // shortcut if lock held by this process 113 } 114 115 if (!file_exists($path = $this->getPath($key))) { 116 return false; 117 } 118 119 $h = fopen($path, 'rb'); 120 flock($h, LOCK_EX | LOCK_NB, $wouldBlock); 121 flock($h, LOCK_UN); // release the lock we just acquired 122 fclose($h); 123 124 return (bool) $wouldBlock; 125 } 126 127 /** 128 * Locates a cached Response for the Request provided. 129 * 130 * @return Response|null A Response instance, or null if no cache entry was found 131 */ 132 public function lookup(Request $request) 133 { 134 $key = $this->getCacheKey($request); 135 136 if (!$entries = $this->getMetadata($key)) { 137 return; 138 } 139 140 // find a cached entry that matches the request. 141 $match = null; 142 foreach ($entries as $entry) { 143 if ($this->requestsMatch(isset($entry[1]['vary'][0]) ? implode(', ', $entry[1]['vary']) : '', $request->headers->all(), $entry[0])) { 144 $match = $entry; 145 146 break; 147 } 148 } 149 150 if (null === $match) { 151 return; 152 } 153 154 $headers = $match[1]; 155 if (file_exists($body = $this->getPath($headers['x-content-digest'][0]))) { 156 return $this->restoreResponse($headers, $body); 157 } 158 159 // TODO the metaStore referenced an entity that doesn't exist in 160 // the entityStore. We definitely want to return nil but we should 161 // also purge the entry from the meta-store when this is detected. 162 } 163 164 /** 165 * Writes a cache entry to the store for the given Request and Response. 166 * 167 * Existing entries are read and any that match the response are removed. This 168 * method calls write with the new list of cache entries. 169 * 170 * @return string The key under which the response is stored 171 * 172 * @throws \RuntimeException 173 */ 174 public function write(Request $request, Response $response) 175 { 176 $key = $this->getCacheKey($request); 177 $storedEnv = $this->persistRequest($request); 178 179 // write the response body to the entity store if this is the original response 180 if (!$response->headers->has('X-Content-Digest')) { 181 $digest = $this->generateContentDigest($response); 182 183 if (false === $this->save($digest, $response->getContent())) { 184 throw new \RuntimeException('Unable to store the entity.'); 185 } 186 187 $response->headers->set('X-Content-Digest', $digest); 188 189 if (!$response->headers->has('Transfer-Encoding')) { 190 $response->headers->set('Content-Length', \strlen($response->getContent())); 191 } 192 } 193 194 // read existing cache entries, remove non-varying, and add this one to the list 195 $entries = array(); 196 $vary = $response->headers->get('vary'); 197 foreach ($this->getMetadata($key) as $entry) { 198 if (!isset($entry[1]['vary'][0])) { 199 $entry[1]['vary'] = array(''); 200 } 201 202 if ($entry[1]['vary'][0] != $vary || !$this->requestsMatch($vary, $entry[0], $storedEnv)) { 203 $entries[] = $entry; 204 } 205 } 206 207 $headers = $this->persistResponse($response); 208 unset($headers['age']); 209 210 array_unshift($entries, array($storedEnv, $headers)); 211 212 if (false === $this->save($key, serialize($entries))) { 213 throw new \RuntimeException('Unable to store the metadata.'); 214 } 215 216 return $key; 217 } 218 219 /** 220 * Returns content digest for $response. 221 * 222 * @return string 223 */ 224 protected function generateContentDigest(Response $response) 225 { 226 return 'en'.hash('sha256', $response->getContent()); 227 } 228 229 /** 230 * Invalidates all cache entries that match the request. 231 * 232 * @throws \RuntimeException 233 */ 234 public function invalidate(Request $request) 235 { 236 $modified = false; 237 $key = $this->getCacheKey($request); 238 239 $entries = array(); 240 foreach ($this->getMetadata($key) as $entry) { 241 $response = $this->restoreResponse($entry[1]); 242 if ($response->isFresh()) { 243 $response->expire(); 244 $modified = true; 245 $entries[] = array($entry[0], $this->persistResponse($response)); 246 } else { 247 $entries[] = $entry; 248 } 249 } 250 251 if ($modified && false === $this->save($key, serialize($entries))) { 252 throw new \RuntimeException('Unable to store the metadata.'); 253 } 254 } 255 256 /** 257 * Determines whether two Request HTTP header sets are non-varying based on 258 * the vary response header value provided. 259 * 260 * @param string $vary A Response vary header 261 * @param array $env1 A Request HTTP header array 262 * @param array $env2 A Request HTTP header array 263 * 264 * @return bool true if the two environments match, false otherwise 265 */ 266 private function requestsMatch($vary, $env1, $env2) 267 { 268 if (empty($vary)) { 269 return true; 270 } 271 272 foreach (preg_split('/[\s,]+/', $vary) as $header) { 273 $key = str_replace('_', '-', strtolower($header)); 274 $v1 = isset($env1[$key]) ? $env1[$key] : null; 275 $v2 = isset($env2[$key]) ? $env2[$key] : null; 276 if ($v1 !== $v2) { 277 return false; 278 } 279 } 280 281 return true; 282 } 283 284 /** 285 * Gets all data associated with the given key. 286 * 287 * Use this method only if you know what you are doing. 288 * 289 * @param string $key The store key 290 * 291 * @return array An array of data associated with the key 292 */ 293 private function getMetadata($key) 294 { 295 if (!$entries = $this->load($key)) { 296 return array(); 297 } 298 299 return unserialize($entries); 300 } 301 302 /** 303 * Purges data for the given URL. 304 * 305 * This method purges both the HTTP and the HTTPS version of the cache entry. 306 * 307 * @param string $url A URL 308 * 309 * @return bool true if the URL exists with either HTTP or HTTPS scheme and has been purged, false otherwise 310 */ 311 public function purge($url) 312 { 313 $http = preg_replace('#^https:#', 'http:', $url); 314 $https = preg_replace('#^http:#', 'https:', $url); 315 316 $purgedHttp = $this->doPurge($http); 317 $purgedHttps = $this->doPurge($https); 318 319 return $purgedHttp || $purgedHttps; 320 } 321 322 /** 323 * Purges data for the given URL. 324 * 325 * @param string $url A URL 326 * 327 * @return bool true if the URL exists and has been purged, false otherwise 328 */ 329 private function doPurge($url) 330 { 331 $key = $this->getCacheKey(Request::create($url)); 332 if (isset($this->locks[$key])) { 333 flock($this->locks[$key], LOCK_UN); 334 fclose($this->locks[$key]); 335 unset($this->locks[$key]); 336 } 337 338 if (file_exists($path = $this->getPath($key))) { 339 unlink($path); 340 341 return true; 342 } 343 344 return false; 345 } 346 347 /** 348 * Loads data for the given key. 349 * 350 * @param string $key The store key 351 * 352 * @return string The data associated with the key 353 */ 354 private function load($key) 355 { 356 $path = $this->getPath($key); 357 358 return file_exists($path) ? file_get_contents($path) : false; 359 } 360 361 /** 362 * Save data for the given key. 363 * 364 * @param string $key The store key 365 * @param string $data The data to store 366 * 367 * @return bool 368 */ 369 private function save($key, $data) 370 { 371 $path = $this->getPath($key); 372 373 if (isset($this->locks[$key])) { 374 $fp = $this->locks[$key]; 375 @ftruncate($fp, 0); 376 @fseek($fp, 0); 377 $len = @fwrite($fp, $data); 378 if (\strlen($data) !== $len) { 379 @ftruncate($fp, 0); 380 381 return false; 382 } 383 } else { 384 if (!file_exists(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) { 385 return false; 386 } 387 388 $tmpFile = tempnam(\dirname($path), basename($path)); 389 if (false === $fp = @fopen($tmpFile, 'wb')) { 390 @unlink($tmpFile); 391 392 return false; 393 } 394 @fwrite($fp, $data); 395 @fclose($fp); 396 397 if ($data != file_get_contents($tmpFile)) { 398 @unlink($tmpFile); 399 400 return false; 401 } 402 403 if (false === @rename($tmpFile, $path)) { 404 @unlink($tmpFile); 405 406 return false; 407 } 408 } 409 410 @chmod($path, 0666 & ~umask()); 411 } 412 413 public function getPath($key) 414 { 415 return $this->root.\DIRECTORY_SEPARATOR.substr($key, 0, 2).\DIRECTORY_SEPARATOR.substr($key, 2, 2).\DIRECTORY_SEPARATOR.substr($key, 4, 2).\DIRECTORY_SEPARATOR.substr($key, 6); 416 } 417 418 /** 419 * Generates a cache key for the given Request. 420 * 421 * This method should return a key that must only depend on a 422 * normalized version of the request URI. 423 * 424 * If the same URI can have more than one representation, based on some 425 * headers, use a Vary header to indicate them, and each representation will 426 * be stored independently under the same cache key. 427 * 428 * @return string A key for the given Request 429 */ 430 protected function generateCacheKey(Request $request) 431 { 432 return 'md'.hash('sha256', $request->getUri()); 433 } 434 435 /** 436 * Returns a cache key for the given Request. 437 * 438 * @return string A key for the given Request 439 */ 440 private function getCacheKey(Request $request) 441 { 442 if (isset($this->keyCache[$request])) { 443 return $this->keyCache[$request]; 444 } 445 446 return $this->keyCache[$request] = $this->generateCacheKey($request); 447 } 448 449 /** 450 * Persists the Request HTTP headers. 451 * 452 * @return array An array of HTTP headers 453 */ 454 private function persistRequest(Request $request) 455 { 456 return $request->headers->all(); 457 } 458 459 /** 460 * Persists the Response HTTP headers. 461 * 462 * @return array An array of HTTP headers 463 */ 464 private function persistResponse(Response $response) 465 { 466 $headers = $response->headers->all(); 467 $headers['X-Status'] = array($response->getStatusCode()); 468 469 return $headers; 470 } 471 472 /** 473 * Restores a Response from the HTTP headers and body. 474 * 475 * @param array $headers An array of HTTP headers for the Response 476 * @param string $body The Response body 477 * 478 * @return Response 479 */ 480 private function restoreResponse($headers, $body = null) 481 { 482 $status = $headers['X-Status'][0]; 483 unset($headers['X-Status']); 484 485 if (null !== $body) { 486 $headers['X-Body-File'] = array($body); 487 } 488 489 return new Response($body, $status, $headers); 490 } 491 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Nov 11 20:33:01 2020 | Cross-referenced by PHPXref 0.7.1 |