Skip to content

Commit 64c4acd

Browse files
committed
Merge pull request #193 from FriendsOfSymfony/symfony-cache-client
adding client for the symfony built-in reverse proxy HttpCache
2 parents 6d27bc0 + df54ddc commit 64c4acd

File tree

14 files changed

+867
-237
lines changed

14 files changed

+867
-237
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Changelog
22
=========
33

4+
1.3.3
5+
-----
6+
7+
* **2015-05-08** Added a client for the Symfony built-in HttpCache
8+
49
1.3.0
510
-----
611

doc/proxy-clients.rst

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Caching Proxy Clients
22
=====================
33

4-
This library ships with clients for the Varnish and NGINX caching proxy. You
4+
This library ships with clients for the Varnish, NGINX and Symfony built-in caching proxies. You
55
can use the clients either wrapped by the :doc:`cache invalidator <cache-invalidator>`
66
(recommended), or directly for low-level access to invalidation functionality.
77

@@ -35,16 +35,16 @@ include that port in the base URL::
3535

3636
.. note::
3737

38-
To use the client, you need to :doc:`configure Varnish <varnish-configuration>` accordingly.
38+
To make invalidation work, you need to :doc:`configure Varnish <varnish-configuration>` accordingly.
3939

4040
NGINX Client
4141
~~~~~~~~~~~~
4242

4343
At minimum, supply an array containing IPs or hostnames of the NGINX servers
4444
that you want to send invalidation requests to. Make sure to include the port
45-
NGINX runs on if it is not port 80::
45+
NGINX runs on if it is not the default::
4646

47-
use FOS\HttpCache\Invalidation\Nginx;
47+
use FOS\HttpCache\ProxyClient\Nginx;
4848

4949
$servers = array('10.0.0.1', '10.0.0.2:8088'); // Port 80 assumed for 10.0.0.1
5050
$nginx = new Nginx($servers);
@@ -64,6 +64,29 @@ supply that location to the class as the third parameter::
6464

6565
To use the client, you need to :doc:`configure NGINX <nginx-configuration>` accordingly.
6666

67+
Symfony Client
68+
~~~~~~~~~~~~~~
69+
70+
At minimum, supply an array containing IPs or hostnames of your web servers
71+
running Symfony. Provide the direct access to the web server without any other
72+
proxies that might block invalidation requests. Make sure to include the port
73+
the web server runs on if it is not the default::
74+
75+
use FOS\HttpCache\ProxyClient\Symfony;
76+
77+
$servers = array('10.0.0.1', '10.0.0.2:8088'); // Port 80 assumed for 10.0.0.1
78+
$client = new Symfony($servers);
79+
80+
This is sufficient for invalidating absolute URLs. If you also wish to
81+
invalidate relative paths, supply the hostname (or base URL) where your website
82+
is available as the second parameter::
83+
84+
$client = new Symfony($servers, 'my-cool-app.com');
85+
86+
.. note::
87+
88+
To make invalidation work, you need to :doc:`use the EventDispatchingHttpCache <symfony-cache-configuration>`.
89+
6790
Using the Clients
6891
-----------------
6992

@@ -200,4 +223,7 @@ send a basic authentication header, you can inject a custom Guzzle client::
200223
$servers = array('10.0.0.1');
201224
$varnish = new Varnish($servers, '/baseUrl', $client);
202225

226+
The Symfony client accepts a guzzle client as the 3rd parameter as well, NGINX
227+
accepts it as 4th parameter.
228+
203229
.. _Guzzle client: http://guzzle3.readthedocs.org/

doc/symfony-cache-configuration.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ not the recommended way. You would need to adjust every place you instantiate
4242
the cache. Instead, overwrite the constructor of AppCache and register the
4343
subscribers there. A simple cache will look like this::
4444

45-
require_once __DIR__.'/AppKernel.php';
46-
4745
use FOS\HttpCache\SymfonyCache\EventDispatchingHttpCache;
4846
use FOS\HttpCache\SymfonyCache\UserContextSubscriber;
4947

@@ -57,6 +55,8 @@ subscribers there. A simple cache will look like this::
5755
parent::__construct($kernel, $cacheDir);
5856

5957
$this->addSubscriber(new UserContextSubscriber());
58+
$this->addSubscriber(new PurgeSubscriber());
59+
$this->addSubscriber(new RefreshSubscriber());
6060
}
6161
}
6262

src/ProxyClient/Symfony.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCache package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace FOS\HttpCache\ProxyClient;
13+
14+
use FOS\HttpCache\Exception\InvalidArgumentException;
15+
use FOS\HttpCache\Exception\MissingHostException;
16+
use FOS\HttpCache\ProxyClient\Invalidation\BanInterface;
17+
use FOS\HttpCache\ProxyClient\Invalidation\PurgeInterface;
18+
use FOS\HttpCache\ProxyClient\Invalidation\RefreshInterface;
19+
use FOS\HttpCache\SymfonyCache\PurgeSubscriber;
20+
use Guzzle\Http\ClientInterface;
21+
use Symfony\Component\OptionsResolver\OptionsResolver;
22+
23+
/**
24+
* Symfony HttpCache invalidator.
25+
*
26+
* @author David de Boer <[email protected]>
27+
* @author David Buchmann <[email protected]>
28+
*/
29+
class Symfony extends AbstractProxyClient implements PurgeInterface, RefreshInterface
30+
{
31+
const HTTP_METHOD_REFRESH = 'GET';
32+
33+
/**
34+
* The options configured in the constructor argument or default values.
35+
*
36+
* @var array
37+
*/
38+
private $options;
39+
40+
/**
41+
* {@inheritDoc}
42+
*
43+
* When creating the client, you can configure options:
44+
*
45+
* - purge_method: HTTP method that identifies purge requests.
46+
*
47+
* @param array $options The purge_method that should be used.
48+
*/
49+
public function __construct(array $servers, $baseUrl = null, ClientInterface $client = null, array $options = array())
50+
{
51+
parent::__construct($servers, $baseUrl, $client);
52+
53+
$resolver = new OptionsResolver();
54+
$resolver->setDefaults(array(
55+
'purge_method' => PurgeSubscriber::DEFAULT_PURGE_METHOD,
56+
));
57+
58+
$this->options = $resolver->resolve($options);
59+
}
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
public function purge($url, array $headers = array())
65+
{
66+
$this->queueRequest($this->options['purge_method'], $url, $headers);
67+
68+
return $this;
69+
}
70+
71+
/**
72+
* {@inheritdoc}
73+
*/
74+
public function refresh($url, array $headers = array())
75+
{
76+
$headers = array_merge($headers, array('Cache-Control' => 'no-cache'));
77+
$this->queueRequest(self::HTTP_METHOD_REFRESH, $url, $headers);
78+
79+
return $this;
80+
}
81+
82+
/**
83+
* {@inheritdoc}
84+
*/
85+
protected function getAllowedSchemes()
86+
{
87+
return array('http', 'https');
88+
}
89+
}

src/SymfonyCache/PurgeSubscriber.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
*/
2828
class PurgeSubscriber extends AccessControlledSubscriber
2929
{
30+
const DEFAULT_PURGE_METHOD = 'PURGE';
31+
3032
/**
3133
* The options configured in the constructor argument or default values.
3234
*
@@ -59,7 +61,7 @@ public function __construct(array $options = array())
5961
$resolver->setDefaults(array(
6062
'purge_client_matcher' => null,
6163
'purge_client_ips' => null,
62-
'purge_method' => 'PURGE',
64+
'purge_method' => static::DEFAULT_PURGE_METHOD,
6365
));
6466

6567
$this->options = $resolver->resolve($options);

src/Test/Proxy/SymfonyProxy.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCache package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace FOS\HttpCache\Test\Proxy;
13+
14+
/**
15+
* Controls the Symfony HttpCache proxy server.
16+
*
17+
* SYMFONY_CACHE_DIR directory to use for cache
18+
* (default sys_get_temp_dir() + '/foshttpcache-symfony')
19+
*/
20+
class SymfonyProxy implements ProxyInterface
21+
{
22+
/**
23+
* Get Symfony cache directory
24+
*
25+
* @return string
26+
*/
27+
public function getCacheDir()
28+
{
29+
return defined('SYMFONY_CACHE_DIR') ? SYMFONY_CACHE_DIR : sys_get_temp_dir() . '/foshttpcache-symfony';
30+
}
31+
32+
/**
33+
* Start the proxy server
34+
*/
35+
public function start()
36+
{
37+
$this->clear();
38+
}
39+
40+
/**
41+
* Stop the proxy server
42+
*/
43+
public function stop()
44+
{
45+
// nothing to do
46+
}
47+
48+
/**
49+
* Clear all cached content from the proxy server
50+
*/
51+
public function clear()
52+
{
53+
if (is_dir($this->getCacheDir())) {
54+
$path = realpath($this->getCacheDir());
55+
if (!$this->getCacheDir() || '/' == $path) {
56+
throw new \Exception('Invalid test setup, the cache dir is ' . $path);
57+
}
58+
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
59+
system('DEL /S '.$path);
60+
} else {
61+
system('rm -r '.$path);
62+
}
63+
}
64+
}
65+
}

src/Test/SymfonyTestCase.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCache package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace FOS\HttpCache\Test;
13+
14+
use FOS\HttpCache\ProxyClient\Symfony;
15+
use FOS\HttpCache\Test\Proxy\SymfonyProxy;
16+
17+
/**
18+
* A phpunit base class to write functional tests with the symfony HttpCache.
19+
*
20+
* The webserver with symfony is to be started with the WebServerListener.
21+
*
22+
* You can define constants in your phpunit to control how this test behaves.
23+
*
24+
* To define constants in the phpunit file, use this syntax:
25+
* <php>
26+
* <const name="WEB_SERVER_PORT" value="/tmp/foo" />
27+
* </php>
28+
*
29+
* WEB_SERVER_PORT port the PHP webserver listens on (default 8080)
30+
*
31+
* Note that the SymfonyProxy also defines a SYMFONY_CACHE_DIR constant.
32+
*/
33+
abstract class SymfonyTestCase extends ProxyTestCase
34+
{
35+
/**
36+
* @var Symfony
37+
*/
38+
protected $proxyClient;
39+
40+
/**
41+
* @var SymfonyProxy
42+
*/
43+
protected $proxy;
44+
45+
/**
46+
* Get server port
47+
*
48+
* @return int
49+
*
50+
* @throws \Exception
51+
*/
52+
protected function getCachingProxyPort()
53+
{
54+
if (!defined('WEB_SERVER_PORT')) {
55+
throw new \Exception('Set WEB_SERVER_PORT in your phpunit.xml');
56+
}
57+
58+
return WEB_SERVER_PORT;
59+
}
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
protected function getProxy()
65+
{
66+
if (null === $this->proxy) {
67+
$this->proxy = new SymfonyProxy();
68+
}
69+
70+
return $this->proxy;
71+
}
72+
73+
/**
74+
* Get Symfony proxy client
75+
*
76+
* We use a non-default method for PURGE because the built-in PHP webserver
77+
* does not allow arbitrary HTTP methods.
78+
* https://github.com/php/php-src/blob/PHP-5.4.1/sapi/cli/php_http_parser.c#L78-L102
79+
*
80+
* @return Symfony
81+
*/
82+
protected function getProxyClient()
83+
{
84+
if (null === $this->proxyClient) {
85+
$this->proxyClient = new Symfony(
86+
array('http://127.0.0.1:' . $this->getCachingProxyPort()),
87+
$this->getHostName() . ':' . $this->getCachingProxyPort(),
88+
null,
89+
array('purge_method' => 'NOTIFY')
90+
);
91+
}
92+
93+
return $this->proxyClient;
94+
}
95+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace FOS\HttpCache\Tests\Functional\Fixtures\Symfony;
4+
5+
use FOS\HttpCache\SymfonyCache\EventDispatchingHttpCache;
6+
use Symfony\Component\HttpFoundation\Request;
7+
use Symfony\Component\HttpKernel\HttpKernelInterface;
8+
9+
class AppCache extends EventDispatchingHttpCache
10+
{
11+
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
12+
{
13+
$response = parent::handle($request, $type, $catch);
14+
15+
if ($response->headers->has('X-Symfony-Cache')) {
16+
if (false !== strpos($response->headers->get('X-Symfony-Cache'), 'miss')) {
17+
$state = 'MISS';
18+
} elseif (false !== strpos($response->headers->get('X-Symfony-Cache'), 'fresh')) {
19+
$state = 'HIT';
20+
} else {
21+
$state = 'UNDETERMINED';
22+
}
23+
$response->headers->set('X-Cache', $state);
24+
}
25+
26+
return $response;
27+
}
28+
}

0 commit comments

Comments
 (0)