Mercurial > hg > isophonics-drupal-site
comparison vendor/symfony/http-kernel/HttpCache/HttpCache.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 1fec387a4317 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
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 * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) | |
11 * | |
12 * For the full copyright and license information, please view the LICENSE | |
13 * file that was distributed with this source code. | |
14 */ | |
15 | |
16 namespace Symfony\Component\HttpKernel\HttpCache; | |
17 | |
18 use Symfony\Component\HttpKernel\HttpKernelInterface; | |
19 use Symfony\Component\HttpKernel\TerminableInterface; | |
20 use Symfony\Component\HttpFoundation\Request; | |
21 use Symfony\Component\HttpFoundation\Response; | |
22 | |
23 /** | |
24 * Cache provides HTTP caching. | |
25 * | |
26 * @author Fabien Potencier <fabien@symfony.com> | |
27 */ | |
28 class HttpCache implements HttpKernelInterface, TerminableInterface | |
29 { | |
30 private $kernel; | |
31 private $store; | |
32 private $request; | |
33 private $surrogate; | |
34 private $surrogateCacheStrategy; | |
35 private $options = array(); | |
36 private $traces = array(); | |
37 | |
38 /** | |
39 * Constructor. | |
40 * | |
41 * The available options are: | |
42 * | |
43 * * debug: If true, the traces are added as a HTTP header to ease debugging | |
44 * | |
45 * * default_ttl The number of seconds that a cache entry should be considered | |
46 * fresh when no explicit freshness information is provided in | |
47 * a response. Explicit Cache-Control or Expires headers | |
48 * override this value. (default: 0) | |
49 * | |
50 * * private_headers Set of request headers that trigger "private" cache-control behavior | |
51 * on responses that don't explicitly state whether the response is | |
52 * public or private via a Cache-Control directive. (default: Authorization and Cookie) | |
53 * | |
54 * * allow_reload Specifies whether the client can force a cache reload by including a | |
55 * Cache-Control "no-cache" directive in the request. Set it to ``true`` | |
56 * for compliance with RFC 2616. (default: false) | |
57 * | |
58 * * allow_revalidate Specifies whether the client can force a cache revalidate by including | |
59 * a Cache-Control "max-age=0" directive in the request. Set it to ``true`` | |
60 * for compliance with RFC 2616. (default: false) | |
61 * | |
62 * * stale_while_revalidate Specifies the default number of seconds (the granularity is the second as the | |
63 * Response TTL precision is a second) during which the cache can immediately return | |
64 * a stale response while it revalidates it in the background (default: 2). | |
65 * This setting is overridden by the stale-while-revalidate HTTP Cache-Control | |
66 * extension (see RFC 5861). | |
67 * | |
68 * * stale_if_error Specifies the default number of seconds (the granularity is the second) during which | |
69 * the cache can serve a stale response when an error is encountered (default: 60). | |
70 * This setting is overridden by the stale-if-error HTTP Cache-Control extension | |
71 * (see RFC 5861). | |
72 * | |
73 * @param HttpKernelInterface $kernel An HttpKernelInterface instance | |
74 * @param StoreInterface $store A Store instance | |
75 * @param SurrogateInterface $surrogate A SurrogateInterface instance | |
76 * @param array $options An array of options | |
77 */ | |
78 public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = array()) | |
79 { | |
80 $this->store = $store; | |
81 $this->kernel = $kernel; | |
82 $this->surrogate = $surrogate; | |
83 | |
84 // needed in case there is a fatal error because the backend is too slow to respond | |
85 register_shutdown_function(array($this->store, 'cleanup')); | |
86 | |
87 $this->options = array_merge(array( | |
88 'debug' => false, | |
89 'default_ttl' => 0, | |
90 'private_headers' => array('Authorization', 'Cookie'), | |
91 'allow_reload' => false, | |
92 'allow_revalidate' => false, | |
93 'stale_while_revalidate' => 2, | |
94 'stale_if_error' => 60, | |
95 ), $options); | |
96 } | |
97 | |
98 /** | |
99 * 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 return $this->surrogate; | |
163 } | |
164 | |
165 /** | |
166 * {@inheritdoc} | |
167 */ | |
168 public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) | |
169 { | |
170 // FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism | |
171 if (HttpKernelInterface::MASTER_REQUEST === $type) { | |
172 $this->traces = array(); | |
173 $this->request = $request; | |
174 if (null !== $this->surrogate) { | |
175 $this->surrogateCacheStrategy = $this->surrogate->createCacheStrategy(); | |
176 } | |
177 } | |
178 | |
179 $path = $request->getPathInfo(); | |
180 if ($qs = $request->getQueryString()) { | |
181 $path .= '?'.$qs; | |
182 } | |
183 $this->traces[$request->getMethod().' '.$path] = array(); | |
184 | |
185 if (!$request->isMethodSafe(false)) { | |
186 $response = $this->invalidate($request, $catch); | |
187 } elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) { | |
188 $response = $this->pass($request, $catch); | |
189 } else { | |
190 $response = $this->lookup($request, $catch); | |
191 } | |
192 | |
193 $this->restoreResponseBody($request, $response); | |
194 | |
195 $response->setDate(\DateTime::createFromFormat('U', time(), new \DateTimeZone('UTC'))); | |
196 | |
197 if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) { | |
198 $response->headers->set('X-Symfony-Cache', $this->getLog()); | |
199 } | |
200 | |
201 if (null !== $this->surrogate) { | |
202 if (HttpKernelInterface::MASTER_REQUEST === $type) { | |
203 $this->surrogateCacheStrategy->update($response); | |
204 } else { | |
205 $this->surrogateCacheStrategy->add($response); | |
206 } | |
207 } | |
208 | |
209 $response->prepare($request); | |
210 | |
211 $response->isNotModified($request); | |
212 | |
213 return $response; | |
214 } | |
215 | |
216 /** | |
217 * {@inheritdoc} | |
218 */ | |
219 public function terminate(Request $request, Response $response) | |
220 { | |
221 if ($this->getKernel() instanceof TerminableInterface) { | |
222 $this->getKernel()->terminate($request, $response); | |
223 } | |
224 } | |
225 | |
226 /** | |
227 * Forwards the Request to the backend without storing the Response in the cache. | |
228 * | |
229 * @param Request $request A Request instance | |
230 * @param bool $catch Whether to process exceptions | |
231 * | |
232 * @return Response A Response instance | |
233 */ | |
234 protected function pass(Request $request, $catch = false) | |
235 { | |
236 $this->record($request, 'pass'); | |
237 | |
238 return $this->forward($request, $catch); | |
239 } | |
240 | |
241 /** | |
242 * Invalidates non-safe methods (like POST, PUT, and DELETE). | |
243 * | |
244 * @param Request $request A Request instance | |
245 * @param bool $catch Whether to process exceptions | |
246 * | |
247 * @return Response A Response instance | |
248 * | |
249 * @throws \Exception | |
250 * | |
251 * @see RFC2616 13.10 | |
252 */ | |
253 protected function invalidate(Request $request, $catch = false) | |
254 { | |
255 $response = $this->pass($request, $catch); | |
256 | |
257 // invalidate only when the response is successful | |
258 if ($response->isSuccessful() || $response->isRedirect()) { | |
259 try { | |
260 $this->store->invalidate($request); | |
261 | |
262 // As per the RFC, invalidate Location and Content-Location URLs if present | |
263 foreach (array('Location', 'Content-Location') as $header) { | |
264 if ($uri = $response->headers->get($header)) { | |
265 $subRequest = Request::create($uri, 'get', array(), array(), array(), $request->server->all()); | |
266 | |
267 $this->store->invalidate($subRequest); | |
268 } | |
269 } | |
270 | |
271 $this->record($request, 'invalidate'); | |
272 } catch (\Exception $e) { | |
273 $this->record($request, 'invalidate-failed'); | |
274 | |
275 if ($this->options['debug']) { | |
276 throw $e; | |
277 } | |
278 } | |
279 } | |
280 | |
281 return $response; | |
282 } | |
283 | |
284 /** | |
285 * Lookups a Response from the cache for the given Request. | |
286 * | |
287 * When a matching cache entry is found and is fresh, it uses it as the | |
288 * response without forwarding any request to the backend. When a matching | |
289 * cache entry is found but is stale, it attempts to "validate" the entry with | |
290 * the backend using conditional GET. When no matching cache entry is found, | |
291 * it triggers "miss" processing. | |
292 * | |
293 * @param Request $request A Request instance | |
294 * @param bool $catch whether to process exceptions | |
295 * | |
296 * @return Response A Response instance | |
297 * | |
298 * @throws \Exception | |
299 */ | |
300 protected function lookup(Request $request, $catch = false) | |
301 { | |
302 // if allow_reload and no-cache Cache-Control, allow a cache reload | |
303 if ($this->options['allow_reload'] && $request->isNoCache()) { | |
304 $this->record($request, 'reload'); | |
305 | |
306 return $this->fetch($request, $catch); | |
307 } | |
308 | |
309 try { | |
310 $entry = $this->store->lookup($request); | |
311 } catch (\Exception $e) { | |
312 $this->record($request, 'lookup-failed'); | |
313 | |
314 if ($this->options['debug']) { | |
315 throw $e; | |
316 } | |
317 | |
318 return $this->pass($request, $catch); | |
319 } | |
320 | |
321 if (null === $entry) { | |
322 $this->record($request, 'miss'); | |
323 | |
324 return $this->fetch($request, $catch); | |
325 } | |
326 | |
327 if (!$this->isFreshEnough($request, $entry)) { | |
328 $this->record($request, 'stale'); | |
329 | |
330 return $this->validate($request, $entry, $catch); | |
331 } | |
332 | |
333 $this->record($request, 'fresh'); | |
334 | |
335 $entry->headers->set('Age', $entry->getAge()); | |
336 | |
337 return $entry; | |
338 } | |
339 | |
340 /** | |
341 * Validates that a cache entry is fresh. | |
342 * | |
343 * The original request is used as a template for a conditional | |
344 * GET request with the backend. | |
345 * | |
346 * @param Request $request A Request instance | |
347 * @param Response $entry A Response instance to validate | |
348 * @param bool $catch Whether to process exceptions | |
349 * | |
350 * @return Response A Response instance | |
351 */ | |
352 protected function validate(Request $request, Response $entry, $catch = false) | |
353 { | |
354 $subRequest = clone $request; | |
355 | |
356 // send no head requests because we want content | |
357 if ('HEAD' === $request->getMethod()) { | |
358 $subRequest->setMethod('GET'); | |
359 } | |
360 | |
361 // add our cached last-modified validator | |
362 $subRequest->headers->set('if_modified_since', $entry->headers->get('Last-Modified')); | |
363 | |
364 // Add our cached etag validator to the environment. | |
365 // We keep the etags from the client to handle the case when the client | |
366 // has a different private valid entry which is not cached here. | |
367 $cachedEtags = $entry->getEtag() ? array($entry->getEtag()) : array(); | |
368 $requestEtags = $request->getETags(); | |
369 if ($etags = array_unique(array_merge($cachedEtags, $requestEtags))) { | |
370 $subRequest->headers->set('if_none_match', implode(', ', $etags)); | |
371 } | |
372 | |
373 $response = $this->forward($subRequest, $catch, $entry); | |
374 | |
375 if (304 == $response->getStatusCode()) { | |
376 $this->record($request, 'valid'); | |
377 | |
378 // return the response and not the cache entry if the response is valid but not cached | |
379 $etag = $response->getEtag(); | |
380 if ($etag && in_array($etag, $requestEtags) && !in_array($etag, $cachedEtags)) { | |
381 return $response; | |
382 } | |
383 | |
384 $entry = clone $entry; | |
385 $entry->headers->remove('Date'); | |
386 | |
387 foreach (array('Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified') as $name) { | |
388 if ($response->headers->has($name)) { | |
389 $entry->headers->set($name, $response->headers->get($name)); | |
390 } | |
391 } | |
392 | |
393 $response = $entry; | |
394 } else { | |
395 $this->record($request, 'invalid'); | |
396 } | |
397 | |
398 if ($response->isCacheable()) { | |
399 $this->store($request, $response); | |
400 } | |
401 | |
402 return $response; | |
403 } | |
404 | |
405 /** | |
406 * Forwards the Request to the backend and determines whether the response should be stored. | |
407 * | |
408 * This methods is triggered when the cache missed or a reload is required. | |
409 * | |
410 * @param Request $request A Request instance | |
411 * @param bool $catch whether to process exceptions | |
412 * | |
413 * @return Response A Response instance | |
414 */ | |
415 protected function fetch(Request $request, $catch = false) | |
416 { | |
417 $subRequest = clone $request; | |
418 | |
419 // send no head requests because we want content | |
420 if ('HEAD' === $request->getMethod()) { | |
421 $subRequest->setMethod('GET'); | |
422 } | |
423 | |
424 // avoid that the backend sends no content | |
425 $subRequest->headers->remove('if_modified_since'); | |
426 $subRequest->headers->remove('if_none_match'); | |
427 | |
428 $response = $this->forward($subRequest, $catch); | |
429 | |
430 if ($response->isCacheable()) { | |
431 $this->store($request, $response); | |
432 } | |
433 | |
434 return $response; | |
435 } | |
436 | |
437 /** | |
438 * Forwards the Request to the backend and returns the Response. | |
439 * | |
440 * @param Request $request A Request instance | |
441 * @param bool $catch Whether to catch exceptions or not | |
442 * @param Response $entry A Response instance (the stale entry if present, null otherwise) | |
443 * | |
444 * @return Response A Response instance | |
445 */ | |
446 protected function forward(Request $request, $catch = false, Response $entry = null) | |
447 { | |
448 if ($this->surrogate) { | |
449 $this->surrogate->addSurrogateCapability($request); | |
450 } | |
451 | |
452 // modify the X-Forwarded-For header if needed | |
453 $forwardedFor = $request->headers->get('X-Forwarded-For'); | |
454 if ($forwardedFor) { | |
455 $request->headers->set('X-Forwarded-For', $forwardedFor.', '.$request->server->get('REMOTE_ADDR')); | |
456 } else { | |
457 $request->headers->set('X-Forwarded-For', $request->server->get('REMOTE_ADDR')); | |
458 } | |
459 | |
460 // fix the client IP address by setting it to 127.0.0.1 as HttpCache | |
461 // is always called from the same process as the backend. | |
462 $request->server->set('REMOTE_ADDR', '127.0.0.1'); | |
463 | |
464 // make sure HttpCache is a trusted proxy | |
465 if (!in_array('127.0.0.1', $trustedProxies = Request::getTrustedProxies())) { | |
466 $trustedProxies[] = '127.0.0.1'; | |
467 Request::setTrustedProxies($trustedProxies, method_exists('Request', 'getTrustedHeaderSet') ? Request::getTrustedHeaderSet() : -1); | |
468 } | |
469 | |
470 // always a "master" request (as the real master request can be in cache) | |
471 $response = $this->kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, $catch); | |
472 // FIXME: we probably need to also catch exceptions if raw === true | |
473 | |
474 // we don't implement the stale-if-error on Requests, which is nonetheless part of the RFC | |
475 if (null !== $entry && in_array($response->getStatusCode(), array(500, 502, 503, 504))) { | |
476 if (null === $age = $entry->headers->getCacheControlDirective('stale-if-error')) { | |
477 $age = $this->options['stale_if_error']; | |
478 } | |
479 | |
480 if (abs($entry->getTtl()) < $age) { | |
481 $this->record($request, 'stale-if-error'); | |
482 | |
483 return $entry; | |
484 } | |
485 } | |
486 | |
487 $this->processResponseBody($request, $response); | |
488 | |
489 if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) { | |
490 $response->setPrivate(); | |
491 } elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControlDirective('must-revalidate')) { | |
492 $response->setTtl($this->options['default_ttl']); | |
493 } | |
494 | |
495 return $response; | |
496 } | |
497 | |
498 /** | |
499 * Checks whether the cache entry is "fresh enough" to satisfy the Request. | |
500 * | |
501 * @param Request $request A Request instance | |
502 * @param Response $entry A Response instance | |
503 * | |
504 * @return bool true if the cache entry if fresh enough, false otherwise | |
505 */ | |
506 protected function isFreshEnough(Request $request, Response $entry) | |
507 { | |
508 if (!$entry->isFresh()) { | |
509 return $this->lock($request, $entry); | |
510 } | |
511 | |
512 if ($this->options['allow_revalidate'] && null !== $maxAge = $request->headers->getCacheControlDirective('max-age')) { | |
513 return $maxAge > 0 && $maxAge >= $entry->getAge(); | |
514 } | |
515 | |
516 return true; | |
517 } | |
518 | |
519 /** | |
520 * Locks a Request during the call to the backend. | |
521 * | |
522 * @param Request $request A Request instance | |
523 * @param Response $entry A Response instance | |
524 * | |
525 * @return bool true if the cache entry can be returned even if it is staled, false otherwise | |
526 */ | |
527 protected function lock(Request $request, Response $entry) | |
528 { | |
529 // try to acquire a lock to call the backend | |
530 $lock = $this->store->lock($request); | |
531 | |
532 // there is already another process calling the backend | |
533 if (true !== $lock) { | |
534 // check if we can serve the stale entry | |
535 if (null === $age = $entry->headers->getCacheControlDirective('stale-while-revalidate')) { | |
536 $age = $this->options['stale_while_revalidate']; | |
537 } | |
538 | |
539 if (abs($entry->getTtl()) < $age) { | |
540 $this->record($request, 'stale-while-revalidate'); | |
541 | |
542 // server the stale response while there is a revalidation | |
543 return true; | |
544 } | |
545 | |
546 // wait for the lock to be released | |
547 $wait = 0; | |
548 while ($this->store->isLocked($request) && $wait < 5000000) { | |
549 usleep(50000); | |
550 $wait += 50000; | |
551 } | |
552 | |
553 if ($wait < 5000000) { | |
554 // replace the current entry with the fresh one | |
555 $new = $this->lookup($request); | |
556 $entry->headers = $new->headers; | |
557 $entry->setContent($new->getContent()); | |
558 $entry->setStatusCode($new->getStatusCode()); | |
559 $entry->setProtocolVersion($new->getProtocolVersion()); | |
560 foreach ($new->headers->getCookies() as $cookie) { | |
561 $entry->headers->setCookie($cookie); | |
562 } | |
563 } else { | |
564 // backend is slow as hell, send a 503 response (to avoid the dog pile effect) | |
565 $entry->setStatusCode(503); | |
566 $entry->setContent('503 Service Unavailable'); | |
567 $entry->headers->set('Retry-After', 10); | |
568 } | |
569 | |
570 return true; | |
571 } | |
572 | |
573 // we have the lock, call the backend | |
574 return false; | |
575 } | |
576 | |
577 /** | |
578 * Writes the Response to the cache. | |
579 * | |
580 * @param Request $request A Request instance | |
581 * @param Response $response A Response instance | |
582 * | |
583 * @throws \Exception | |
584 */ | |
585 protected function store(Request $request, Response $response) | |
586 { | |
587 if (!$response->headers->has('Date')) { | |
588 $response->setDate(\DateTime::createFromFormat('U', time())); | |
589 } | |
590 try { | |
591 $this->store->write($request, $response); | |
592 | |
593 $this->record($request, 'store'); | |
594 | |
595 $response->headers->set('Age', $response->getAge()); | |
596 } catch (\Exception $e) { | |
597 $this->record($request, 'store-failed'); | |
598 | |
599 if ($this->options['debug']) { | |
600 throw $e; | |
601 } | |
602 } | |
603 | |
604 // now that the response is cached, release the lock | |
605 $this->store->unlock($request); | |
606 } | |
607 | |
608 /** | |
609 * Restores the Response body. | |
610 * | |
611 * @param Request $request A Request instance | |
612 * @param Response $response A Response instance | |
613 */ | |
614 private function restoreResponseBody(Request $request, Response $response) | |
615 { | |
616 if ($request->isMethod('HEAD') || 304 === $response->getStatusCode()) { | |
617 $response->setContent(null); | |
618 $response->headers->remove('X-Body-Eval'); | |
619 $response->headers->remove('X-Body-File'); | |
620 | |
621 return; | |
622 } | |
623 | |
624 if ($response->headers->has('X-Body-Eval')) { | |
625 ob_start(); | |
626 | |
627 if ($response->headers->has('X-Body-File')) { | |
628 include $response->headers->get('X-Body-File'); | |
629 } else { | |
630 eval('; ?>'.$response->getContent().'<?php ;'); | |
631 } | |
632 | |
633 $response->setContent(ob_get_clean()); | |
634 $response->headers->remove('X-Body-Eval'); | |
635 if (!$response->headers->has('Transfer-Encoding')) { | |
636 $response->headers->set('Content-Length', strlen($response->getContent())); | |
637 } | |
638 } elseif ($response->headers->has('X-Body-File')) { | |
639 $response->setContent(file_get_contents($response->headers->get('X-Body-File'))); | |
640 } else { | |
641 return; | |
642 } | |
643 | |
644 $response->headers->remove('X-Body-File'); | |
645 } | |
646 | |
647 protected function processResponseBody(Request $request, Response $response) | |
648 { | |
649 if (null !== $this->surrogate && $this->surrogate->needsParsing($response)) { | |
650 $this->surrogate->process($request, $response); | |
651 } | |
652 } | |
653 | |
654 /** | |
655 * Checks if the Request includes authorization or other sensitive information | |
656 * that should cause the Response to be considered private by default. | |
657 * | |
658 * @param Request $request A Request instance | |
659 * | |
660 * @return bool true if the Request is private, false otherwise | |
661 */ | |
662 private function isPrivateRequest(Request $request) | |
663 { | |
664 foreach ($this->options['private_headers'] as $key) { | |
665 $key = strtolower(str_replace('HTTP_', '', $key)); | |
666 | |
667 if ('cookie' === $key) { | |
668 if (count($request->cookies->all())) { | |
669 return true; | |
670 } | |
671 } elseif ($request->headers->has($key)) { | |
672 return true; | |
673 } | |
674 } | |
675 | |
676 return false; | |
677 } | |
678 | |
679 /** | |
680 * Records that an event took place. | |
681 * | |
682 * @param Request $request A Request instance | |
683 * @param string $event The event name | |
684 */ | |
685 private function record(Request $request, $event) | |
686 { | |
687 $path = $request->getPathInfo(); | |
688 if ($qs = $request->getQueryString()) { | |
689 $path .= '?'.$qs; | |
690 } | |
691 $this->traces[$request->getMethod().' '.$path][] = $event; | |
692 } | |
693 } |