forked from liip/LiipCacheControlBundle
/
Varnish.php
179 lines (149 loc) · 5.66 KB
/
Varnish.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
<?php
namespace Liip\CacheControlBundle\Helper;
/**
* Helper to invalidate or force a refresh varnish entries
*
* Supports multiple varnish instances.
*
* For invalidation uses PURGE requests to the frontend.
* See http://www.varnish-cache.org/trac/wiki/VCLExamplePurging
*
* This is about equivalent to doing this
*
* netcat localhost 6081 << EOF
* PURGE /url/to/purge HTTP/1.1
* Host: webapp-host.name
*
* EOF
*
* For a forced refresh it uses a normal GET with appropriate cache headers
* See: http://www.varnish-cache.org/trac/wiki/VCLExampleEnableForceRefresh
*
* This is about equivalent to doing this
*
* netcat localhost 6081 << EOF
* GET /url/to/refresh HTTP/1.1
* Host: webapp-host.name
* Cache-Control: no-cache, no-store, max-age=0, must-revalidate
*
* EOF
*
* TODO: would be nice to support the varnish admin shell as well. It would be
* more clean and secure, but you have to configure varnish accordingly. By default
* the admin port is only open for local host for security reasons.
*/
class Varnish
{
private $ips;
private $domain;
private $port;
private $lastRequestError;
private $lastRequestInfo;
/**
* Constructor
*
* @param string $domain the domain we want to purge urls from. only domain and port are used, path is ignored
* @param array $ips space separated list of varnish ips to talk to
* @param int $port the port the varnishes listen on (its the same port for all instances)
*/
public function __construct($domain, array $ips, $port)
{
$url = parse_url($domain);
$this->domain = $url['host'];
if (isset($url['port'])) {
$this->domain .= ':' . $url['port'];
}
$this->ips = $ips;
$this->port = $port;
}
/**
* Purge this absolute path at all registered cache server
*
* @param string $path Must be an absolute path
* @param array $options Options for cUrl Request
*
* @return array An associative array with keys 'headers' and 'body' which holds a raw response from the server
*
* @throws \RuntimeException if connection to one of the varnish servers fails.
*/
public function invalidatePath($path, array $options = array())
{
//Garanteed to be a purge request
$options[CURLOPT_CUSTOMREQUEST] = 'PURGE';
$request = array('path' => $path);
return $this->sendRequestToAllVarnishes($request, $options);
}
/**
* Force this absolute path to be refreshed
*
* @param string $path Must be an absolute path
* @param array $options Options for cUrl Request
*
* @return array An associative array with keys 'headers' and 'body' which holds a raw response from the server
* @throws \RuntimeException if connection to one of the varnish servers fails.
*/
public function refreshPath($path, array $options = array())
{
$headers = array("Cache-Control: no-cache, no-store, max-age=0, must-revalidate");
$options[CURLOPT_HTTPHEADER] = $headers;
$options[CURLOPT_CUSTOMREQUEST] = 'GET';
$request = array('path' => $path);
return $this->sendRequestToAllVarnishes($request, $options);
}
/**
* Send a request to all configured varnishes
*
* @param array $request request string
* @param array $options Options for request
*
* @return array An associative array with keys 'headers', 'body', 'error' and 'errorNumber' for each configured Ip
* @throws \RuntimeException if connection to one of the varnish servers fails. TODO: should we be more tolerant?
*/
protected function sendRequestToAllVarnishes($request, array $options = array())
{
$requestResponseByIp = array();
$curlHandler = curl_init($this->domain);
$hostHttpHeader = sprintf('Host: %s', $this->domain);
if (!isset($options[CURLOPT_HTTPHEADER])) {
$options[CURLOPT_HTTPHEADER] = array($hostHttpHeader);
} else {
$hostHttpHeaderFound = false;
foreach ($options[CURLOPT_HTTPHEADER] as $httpHeader) {
if (strpos(strtolower($httpHeader), 'host:') === 0) {
$hostHttpHeaderFound = true;
break;
}
}
if (!$hostHttpHeaderFound) {
array_push($options[CURLOPT_HTTPHEADER], $hostHttpHeader);
}
}
foreach ($options as $option => $value) {
curl_setopt($curlHandler, (int) $option, $value);
}
//Default Options
curl_setopt($curlHandler, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlHandler, CURLOPT_HEADER, true); // Display headers
foreach ($this->ips as $ip) {
curl_setopt($curlHandler, CURLOPT_URL, $ip.':'.$this->port.$request['path']);
$response = curl_exec($curlHandler);
//Failed
if ($response === false) {
$header = '';
$body = '';
$error = curl_error($curlHandler);
$errorNumber = curl_errno($curlHandler);
} else {
$error = null;
$errorNumber = CURLE_OK;
list($header, $body) = explode("\r\n\r\n", $response, 2);
}
$requestResponseByIp[$ip] = array('headers' => $header,
'body' => $body,
'error' => $error,
'errorNumber' => $errorNumber);
}
curl_close($curlHandler);
return $requestResponseByIp;
}
}