Skip to content

Commit d065005

Browse files
committed
Added LiteSpeed support
1 parent a2fce47 commit d065005

File tree

6 files changed

+377
-1
lines changed

6 files changed

+377
-1
lines changed

doc/cache-invalidator.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Create the cache invalidator by passing a proxy client as
1818
// or
1919
$client = new ProxyClient\Nginx(...);
2020
// or
21+
$client = new ProxyClient\LiteSpeed(...);
22+
// or
2123
$client = new ProxyClient\Symfony(...);
2224
// or, for local development
2325
$client = new ProxyClient\Noop();

doc/litespeed-configuration.rst

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
.. _litespeed configuration:
2+
3+
LiteSpeed Configuration
4+
-----------------------
5+
6+
Below you will find detailed LiteSpeed configuration recommendations for the
7+
features provided by this library.
8+
9+
Preamble
10+
~~~~~~~~
11+
12+
First of all, let's get one thing straight here: You'll find a lot of documentation
13+
and noise around LiteSpeed cache on the Internet, mostly involving plugins, specifically the
14+
Wordpress one. You **don't** need any plugin to benefit from LiteSpeed cache!
15+
As long as you follow the HTTP specification regarding the caching headers, you can use it as
16+
a general reverse proxy like NGINX or Varnish.
17+
18+
Invalidation works by setting the specific LiteSpeed headers on the **response**. This means
19+
contrary to other proxies in this library, we do not send any ``PURGE`` requests to
20+
the proxy but instead we have to send a request to an endpoint where the response provides
21+
the correct LiteSpeed-specific headers which then trigger purging actions.
22+
You can read more on these headers in the `LiteSpeed response headers documentation`_.
23+
24+
To do so, we generate a simple PHP file with a random file name containing the appropriate ``header()`` calls.
25+
After generation, we request this file and delete it again right away.
26+
27+
For this reason you have to configure two parameters:
28+
29+
* The location on your server where the file should be generated to (must be publicly accessible).
30+
* The base URL on which the generated file can be requested.
31+
32+
Configuring LiteSpeed WebServer itself
33+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
34+
35+
Enabling LiteSpeed to support public caching on your server is pretty much straight forward.
36+
Add this to your ``.htaccess``::
37+
38+
<IfModule LiteSpeed>
39+
CacheEnable public /
40+
</IfModule>
41+
42+
If you also want to enable ESI support, you need to enable this as well::
43+
44+
<IfModule LiteSpeed>
45+
CacheEnable public /
46+
RewriteRule .? - [E=esi_on:1]
47+
</IfModule>
48+
49+
Depending on your setup you might also make your app aware of the fact that your
50+
server supports ESI. E.g. Symfony checks the request header called ``Surrogate-Capability``.
51+
In case of Symfony your full setup might thus look as follows::
52+
53+
54+
<IfModule LiteSpeed>
55+
CacheEnable public /
56+
RewriteRule .? - [E=esi_on:1]
57+
SetEnv HTTP_SURROGATE_CAPABILITY "litespeed=ESI/1.0"
58+
</IfModule>
59+
60+
.. note::
61+
62+
ESI is not supported in OpenLiteSpeed.
63+
You must be using LiteSpeed Enterprise in order to take advantage of ESI functionality.
64+
65+
66+
You can find more information on how to `configure LiteSpeed`_ and how to `configure ESI`_ support in their docs.
67+
68+
Configuring the library
69+
~~~~~~~~~~~~~~~~~~~~~~~
70+
71+
To illustrate configuration it's easiest if we do so with an example. For this we assume you have the following setup:
72+
73+
* Your domain is called ``www.example.com``
74+
* Your domain points to ``/var/www/public``
75+
76+
77+
Your proxy client instance has to look like so::
78+
79+
use FOS\HttpCache\ProxyClient\HttpDispatcher;
80+
use FOS\HttpCache\ProxyClient\LiteSpeed;
81+
82+
$servers = ['https://www.example.com'];
83+
$baseUri = 'https://www.example.com';
84+
$httpDispatcher = new HttpDispatcher($servers, $baseUri);
85+
86+
$options = [
87+
'target_dir' => '/var/www/public',
88+
];
89+
90+
$litespeed = new LiteSpeed($httpDispatcher, $options);
91+
92+
.. _configure LiteSpeed: https://www.litespeedtech.com/support/wiki/doku.php/litespeed_wiki:cache:no-plugin-setup-guidline
93+
.. _configure ESI: https://www.litespeedtech.com/support/wiki/doku.php/litespeed_wiki:cache:no-plugin-advanced:esi-support
94+
.. _LiteSpeed response headers documentation: https://www.litespeedtech.com/support/wiki/doku.php/litespeed_wiki:cache:developer_guide:response_headers

doc/proxy-clients.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Client Purge Refresh Ban Tagging Clear
2424
============= ======= ======= ======= ======= =======
2525
Varnish ✓ ✓ ✓ ✓
2626
NGINX ✓ ✓
27+
LiteSpeed ✓ ✓ ✓ ✓
2728
Symfony Cache ✓ ✓ ✓ (1) ✓ (1)
2829
Noop ✓ ✓ ✓ ✓
2930
Multiplexer ✓ ✓ ✓ ✓
@@ -181,6 +182,21 @@ call `setPurgeLocation()`::
181182
To use the client, you need to :doc:`configure NGINX <nginx-configuration>`
182183
accordingly.
183184

185+
LiteSpeed Client
186+
~~~~~~~~~~~~~~~~~
187+
188+
The LiteSpeed client sends HTTP requests with the ``HttpDispatcher``. Create the
189+
dispatcher as explained above and pass it to the LiteSpeed client::
190+
191+
use FOS\HttpCache\ProxyClient\LiteSpeed;
192+
193+
$litespeed = new LiteSpeed($httpDispatcher);
194+
195+
.. note::
196+
197+
To use the client, you need to :doc:`configure LiteSpeed <litespeed-configuration>`
198+
accordingly.
199+
184200
Symfony Client
185201
~~~~~~~~~~~~~~
186202

doc/proxy-configuration.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Proxy Server Configuration
44
==========================
55

6-
You need to configure the proxy server of your choice (Varnish, NGINX or Symfony
6+
You need to configure the proxy server of your choice (Varnish, NGINX, LiteSpeed or Symfony
77
HttpCache) to work with FOSHttpCache. These guides help you
88
for the configuration for the features of this library. You will still need to
99
know about the other features of the proxy server to get everything right.
@@ -12,4 +12,5 @@ know about the other features of the proxy server to get everything right.
1212

1313
varnish-configuration
1414
nginx-configuration
15+
litespeed-configuration
1516
symfony-cache-configuration

src/ProxyClient/LiteSpeed.php

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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\ProxyClient\Invalidation\ClearCapable;
15+
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
16+
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
17+
18+
/**
19+
* LiteSpeed Web Server (LSWS) invalidator.
20+
*
21+
* @author Yanick Witschi <[email protected]>
22+
*/
23+
class LiteSpeed extends HttpProxyClient implements PurgeCapable, TagCapable, ClearCapable
24+
{
25+
private $headerLines = [];
26+
27+
/**
28+
* {@inheritdoc}
29+
*/
30+
public function clear()
31+
{
32+
$this->headerLines[] = 'X-LiteSpeed-Purge: *';
33+
34+
return $this;
35+
}
36+
37+
/**
38+
* {@inheritdoc}
39+
*/
40+
public function purge($url, array $headers = [])
41+
{
42+
$this->headerLines[] = 'X-LiteSpeed-Purge: '.$url;
43+
44+
return $this;
45+
}
46+
47+
/**
48+
* {@inheritdoc}
49+
*/
50+
protected function configureOptions()
51+
{
52+
$resolver = parent::configureOptions();
53+
54+
$resolver->setRequired(['target_dir']);
55+
$resolver->setAllowedTypes('target_dir', 'string');
56+
57+
return $resolver;
58+
}
59+
60+
/**
61+
* {@inheritdoc}
62+
*/
63+
public function invalidateTags(array $tags)
64+
{
65+
$this->headerLines[] = 'X-LiteSpeed-Purge: '.implode(', ', preg_filter('/^/', 'tag=', $tags));
66+
67+
return $this;
68+
}
69+
70+
/**
71+
* {@inheritdoc}
72+
*/
73+
public function flush()
74+
{
75+
$filename = $this->createFile();
76+
77+
$url = '/'.$filename;
78+
79+
$this->queueRequest('GET', $url, []);
80+
81+
$result = parent::flush();
82+
83+
// Reset
84+
$this->headerLines = [];
85+
unlink($this->options['target_dir'].'/'.$filename);
86+
87+
return $result;
88+
}
89+
90+
/**
91+
* Creates the file and returns the file name.
92+
*
93+
* @return string
94+
*/
95+
private function createFile()
96+
{
97+
$content = '<?php'."\n\n";
98+
99+
foreach ($this->headerLines as $header) {
100+
$content .= sprintf('header(\'%s\');', $header)."\n";
101+
}
102+
103+
$filename = $this->generateUrlSafeRandomFileName();
104+
105+
file_put_contents($this->options['target_dir'].'/'.$filename, $content);
106+
107+
return $filename;
108+
}
109+
110+
private function generateUrlSafeRandomFileName()
111+
{
112+
$filename = 'fos_cache_litespeed_purger_';
113+
114+
if (function_exists('random_bytes')) {
115+
$filename .= bin2hex(random_bytes(20));
116+
} else {
117+
$filename .= sha1(mt_rand().mt_rand().mt_rand());
118+
}
119+
120+
$filename .= '.php';
121+
122+
return $filename;
123+
}
124+
}

0 commit comments

Comments
 (0)