Created
January 3, 2016 16:33
-
-
Save dleone81/6755d0d8efd3253079fe to your computer and use it in GitHub Desktop.
Adding ESI support to PHOENIX-PageCache
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
C{ | |
#include <errno.h> | |
#include <netinet/in.h> | |
#include <pthread.h> | |
#include <stdlib.h> | |
#include <string.h> | |
typedef void vas_f(const char *, const char *, int, const char *, int, int); | |
extern vas_f *VAS_Fail; | |
#define assert(e) \ | |
do { \ | |
if (!(e)) \ | |
VAS_Fail(__func__, __FILE__, __LINE__, #e, errno, 0); \ | |
} while (0) | |
#define AZ(foo) do { assert((foo) == 0); } while (0) | |
#define AN(foo) do { assert((foo) != 0); } while (0) | |
#define CHECK_OBJ_NOTNULL(ptr, type_magic) \ | |
do { \ | |
assert((ptr) != NULL); \ | |
assert((ptr)->magic == type_magic); \ | |
} while (0) | |
#define ALLOC_OBJ(to, type_magic) \ | |
do { \ | |
(to) = calloc(sizeof *(to), 1); \ | |
if ((to) != NULL) \ | |
(to)->magic = (type_magic); \ | |
} while (0) | |
struct sess { | |
unsigned magic; | |
#define SESS_MAGIC 0x2c2f9c5a | |
int fd; | |
int id; | |
unsigned xid; | |
/* For the sake of inlining this, pretend struct sess ends | |
here ... */ | |
}; | |
struct var { | |
unsigned magic; | |
#define VAR_MAGIC 0xbbd57783 | |
unsigned xid; | |
char *value; | |
}; | |
static struct var **var_list = NULL; | |
static int var_list_sz = 0; | |
static pthread_mutex_t var_list_mtx = PTHREAD_MUTEX_INITIALIZER; | |
static void | |
var_clean(struct var *v) | |
{ | |
CHECK_OBJ_NOTNULL(v, VAR_MAGIC); | |
free(v->value); | |
v->value = NULL; | |
} | |
int | |
init_function(struct vmod_priv *priv, const struct VCL_conf *conf) | |
{ | |
AZ(pthread_mutex_lock(&var_list_mtx)); | |
if (var_list == NULL) { | |
AZ(var_list_sz); | |
var_list_sz = 256; | |
var_list = malloc(sizeof(struct var *) * 256); | |
AN(var_list); | |
int i; | |
for (i = 0 ; i < var_list_sz; i++) { | |
ALLOC_OBJ(var_list[i], VAR_MAGIC); | |
var_list[i]->xid = 0; | |
var_list[i]->value = NULL; | |
} | |
} | |
AZ(pthread_mutex_unlock(&var_list_mtx)); | |
return (0); | |
} | |
static struct var * | |
get_var(struct sess *sp) | |
{ | |
struct var *v; | |
AZ(pthread_mutex_lock(&var_list_mtx)); | |
while (var_list_sz <= sp->id) { | |
int ns = var_list_sz*2; | |
/* resize array */ | |
var_list = realloc(var_list, ns * sizeof(struct var_entry *)); | |
for (; var_list_sz < ns; var_list_sz++) { | |
ALLOC_OBJ(var_list[var_list_sz], VAR_MAGIC); | |
var_list[var_list_sz]->xid = 0; | |
var_list[var_list_sz]->value = NULL; | |
} | |
assert(var_list_sz == ns); | |
AN(var_list); | |
} | |
v = var_list[sp->id]; | |
if (v->xid != sp->xid) { | |
var_clean(v); | |
v->xid = sp->xid; | |
} | |
AZ(pthread_mutex_unlock(&var_list_mtx)); | |
return (v); | |
} | |
void | |
vmod_set(struct sess *sp, const char *value) | |
{ | |
struct var *v = get_var(sp); | |
CHECK_OBJ_NOTNULL(v, VAR_MAGIC); | |
var_clean(v); | |
if (value == NULL) | |
value = ""; | |
v->value = strdup(value); | |
} | |
const char * | |
vmod_get(struct sess *sp) | |
{ | |
struct var *v = get_var(sp); | |
CHECK_OBJ_NOTNULL(v, VAR_MAGIC); | |
return (v->value); | |
} | |
}C | |
sub vcl_init { | |
C{ | |
init_function(NULL, NULL); | |
}C | |
} | |
# input: req.http.x-var-input | |
sub var_set { | |
C{ | |
vmod_set(sp, VRT_GetHdr(sp, HDR_REQ, "\014X-var-input:")); | |
}C | |
} | |
# output: req.http.x-var-output | |
sub var_get { | |
C{ | |
VRT_SetHdr(sp, HDR_REQ, "\015X-var-output:", vmod_get(sp), vrt_magic_string_end); | |
}C | |
} | |
ubuntu@st01-mage:/etc/varnish$ | |
ubuntu@st01-mage:/etc/varnish$ | |
ubuntu@st01-mage:/etc/varnish$ clear | |
ubuntu@st01-mage:/etc/varnish$ sudo pico default_3.0.vcl | |
ubuntu@st01-mage:/etc/varnish$ cat default_3.0.vcl | |
# This is a basic VCL configuration file for PageCache powered by Varnish for Magento module. | |
# include variable handling methods | |
include "vars.vcl"; | |
# default backend definition. Set this to point to your content server. | |
backend default { | |
.host = "127.0.0.1"; | |
.port = "8080"; | |
} | |
# admin backend with longer timeout values. Set this to the same IP & port as your default server. | |
backend admin { | |
.host = "127.0.0.1"; | |
.port = "8080"; | |
.first_byte_timeout = 18000s; | |
.between_bytes_timeout = 18000s; | |
} | |
# add your Magento server IP to allow purges from the backend | |
acl purge { | |
"localhost"; | |
"127.0.0.1"; | |
} | |
import std; | |
sub vcl_init { | |
C{ | |
/* set random salt */ | |
srand(time(NULL)); | |
/* init var storage */ | |
init_function(NULL, NULL); | |
}C | |
} | |
sub vcl_recv { | |
if (req.restarts == 0) { | |
if (req.http.x-forwarded-for) { | |
set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip; | |
} else { | |
set req.http.X-Forwarded-For = client.ip; | |
} | |
} | |
if (req.request != "GET" && | |
req.request != "HEAD" && | |
req.request != "PUT" && | |
req.request != "POST" && | |
req.request != "TRACE" && | |
req.request != "OPTIONS" && | |
req.request != "DELETE" && | |
req.request != "PURGE") { | |
/* Non-RFC2616 or CONNECT which is weird. */ | |
return (pipe); | |
} | |
# purge request | |
if (req.request == "PURGE") { | |
if (!client.ip ~ purge) { | |
error 405 "Not allowed."; | |
} | |
ban("obj.http.X-Purge-Host ~ " + req.http.X-Purge-Host + " && obj.http.X-Purge-URL ~ " + req.http.X-Purge-Regex + " && obj.http.Content-Type ~ " + req.http.X-Purge-Content-Type); | |
error 200 "Purged."; | |
} | |
# switch to admin backend configuration | |
if (req.http.cookie ~ "adminhtml=") { | |
set req.backend = admin; | |
} | |
# we only deal with GET and HEAD by default | |
if (req.request != "GET" && req.request != "HEAD") { | |
return (pass); | |
} | |
# normalize url in case of leading HTTP scheme and domain | |
set req.url = regsub(req.url, "^http[s]?://[^/]+", ""); | |
# collect all cookies | |
std.collect(req.http.Cookie); | |
# static files are always cacheable. remove SSL flag and cookie | |
if (req.url ~ "^/(media|js|skin)/.*\.(png|jpg|jpeg|gif|css|js|swf|ico)$") { | |
unset req.http.Https; | |
unset req.http.Cookie; | |
} | |
# check if we have a formkey cookie | |
if (req.http.Cookie ~ "PAGECACHE_FORMKEY") { | |
set req.http.x-var-input = regsub(req.http.cookie, ".*PAGECACHE_FORMKEY=([^;]*)(;*.*)?", "\1"); | |
call var_set; | |
} else { | |
# create formkey once | |
if (req.esi_level == 0) { | |
C{ | |
generate_formkey(sp, 16); | |
}C | |
set req.http.x-var-input = req.http.X-Pagecache-Formkey; | |
call var_set; | |
} | |
} | |
# cleanup variables | |
unset req.http.x-var-input; | |
unset req.http.X-Pagecache-Formkey; | |
# formkey lookup | |
if (req.url ~ "/varnishcache/getformkey/") { | |
call var_get; | |
error 760 req.http.x-var-output; | |
} | |
# not cacheable by default | |
if (req.http.Authorization || req.http.Https) { | |
return (pass); | |
} | |
# do not cache any page from index files | |
if (req.url ~ "^/(index)") { | |
return (pass); | |
} | |
# as soon as we have a NO_CACHE cookie pass request | |
if (req.http.cookie ~ "NO_CACHE=") { | |
return (pass); | |
} | |
# remove Google gclid parameters | |
set req.url = regsuball(req.url, "\?gclid=[^&]+$", ""); # strips when QS = "?gclid=AAA" | |
set req.url = regsuball(req.url, "\?gclid=[^&]+&", "?"); # strips when QS = "?gclid=AAA&foo=bar" | |
set req.url = regsuball(req.url, "&gclid=[^&]+", ""); # strips when QS = "?foo=bar&gclid=AAA" or QS = "?foo=bar&gclid=AAA&bar=baz" | |
return (lookup); | |
} | |
# sub vcl_pipe { | |
# # Note that only the first request to the backend will have | |
# # X-Forwarded-For set. If you use X-Forwarded-For and want to | |
# # have it set for all requests, make sure to have: | |
# # set bereq.http.connection = "close"; | |
# # here. It is not set by default as it might break some broken web | |
# # applications, like IIS with NTLM authentication. | |
# return (pipe); | |
# } | |
# sub vcl_pass { | |
# return (pass); | |
# } | |
sub vcl_hash { | |
hash_data(req.url); | |
if (req.http.host) { | |
hash_data(req.http.host); | |
} else { | |
hash_data(server.ip); | |
} | |
if (req.http.cookie ~ "PAGECACHE_ENV=") { | |
set req.http.pageCacheEnv = regsub( | |
req.http.cookie, | |
"(.*)PAGECACHE_ENV=([^;]*)(.*)", | |
"\2" | |
); | |
hash_data(req.http.pageCacheEnv); | |
remove req.http.pageCacheEnv; | |
} | |
if (!(req.url ~ "^/(media|js|skin)/.*\.(png|jpg|jpeg|gif|css|js|swf|ico)$")) { | |
call design_exception; | |
} | |
return (hash); | |
} | |
# sub vcl_hit { | |
# return (deliver); | |
# } | |
# sub vcl_miss { | |
# return (fetch); | |
# } | |
sub vcl_fetch { | |
if (beresp.status >= 500) { | |
if (beresp.http.Content-Type ~ "text/xml") { | |
return (deliver); | |
} | |
set beresp.saintmode = 10s; | |
return (restart); | |
} | |
set beresp.grace = 5m; | |
# enable ESI feature if needed | |
if (beresp.http.X-Cache-DoEsi == "1") { | |
set beresp.do_esi = true; | |
#APH do ESI as https://www.varnish-cache.org/docs/3.0/tutorial/esi.html | |
set beresp.ttl = 24 h; | |
} | |
# add ban-lurker tags to object | |
set beresp.http.X-Purge-URL = req.url; | |
set beresp.http.X-Purge-Host = req.http.host; | |
if (beresp.status == 200 || beresp.status == 301 || beresp.status == 404) { | |
if (beresp.http.Content-Type ~ "text/html" || beresp.http.Content-Type ~ "text/xml") { | |
if ((beresp.http.Set-Cookie ~ "NO_CACHE=") || (beresp.ttl < 1s)) { | |
set beresp.ttl = 0s; | |
return (hit_for_pass); | |
} | |
# marker for vcl_deliver to reset Age: | |
set beresp.http.magicmarker = "1"; | |
# Don't cache cookies | |
unset beresp.http.set-cookie; | |
} else { | |
# set default TTL value for static content | |
set beresp.ttl = 4h; | |
} | |
return (deliver); | |
} | |
return (hit_for_pass); | |
} | |
sub vcl_deliver { | |
# debug info | |
if (resp.http.X-Cache-Debug) { | |
if (obj.hits > 0) { | |
set resp.http.X-Cache = "HIT"; | |
set resp.http.X-Cache-Hits = obj.hits; | |
} else { | |
set resp.http.X-Cache = "MISS"; | |
} | |
set resp.http.X-Cache-Expires = resp.http.Expires; | |
} else { | |
# remove Varnish/proxy header | |
remove resp.http.X-Varnish; | |
remove resp.http.Via; | |
remove resp.http.Age; | |
remove resp.http.X-Purge-URL; | |
remove resp.http.X-Purge-Host; | |
} | |
if (resp.http.magicmarker) { | |
# Remove the magic marker | |
unset resp.http.magicmarker; | |
set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, post-check=0, pre-check=0"; | |
set resp.http.Pragma = "no-cache"; | |
set resp.http.Expires = "Mon, 31 Mar 2008 10:00:00 GMT"; | |
set resp.http.Age = "0"; | |
} | |
} | |
sub vcl_error { | |
# workaround for possible security issue | |
if (req.url ~ "^\s") { | |
set obj.status = 400; | |
set obj.response = "Malformed request"; | |
synthetic ""; | |
return(deliver); | |
} | |
# formkey request | |
if (obj.status == 760) { | |
set obj.status = 200; | |
synthetic obj.response; | |
return(deliver); | |
} | |
# error 200 | |
if (obj.status == 200) { | |
return (deliver); | |
} | |
set obj.http.Content-Type = "text/html; charset=utf-8"; | |
set obj.http.Retry-After = "5"; | |
synthetic {" | |
<?xml version="1.0" encoding="utf-8"?> | |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |
<html> | |
<head> | |
<title>"} + obj.status + " " + obj.response + {"</title> | |
</head> | |
<body> | |
<h1>Error "} + obj.status + " " + obj.response + {"</h1> | |
<p>"} + obj.response + {"</p> | |
<h3>Guru Meditation:</h3> | |
<p>XID: "} + req.xid + {"</p> | |
<hr> | |
<p>Varnish cache server</p> | |
</body> | |
</html> | |
"}; | |
return (deliver); | |
} | |
# sub vcl_fini { | |
# return (ok); | |
# } | |
sub design_exception { | |
} | |
C{ | |
#include <string.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <time.h> | |
/** | |
* create a random alphanumeric string and store it in | |
* the request header as X-Pagecache-Formkey | |
*/ | |
char *generate_formkey(struct sess *sp, int maxLength) { | |
char *validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; | |
int validCharsLength = strlen(validChars); | |
char *result = (char *) malloc(maxLength + 1); | |
// generate string | |
int i; | |
for (i = 0; i < maxLength; ++i) { | |
int charPosition = rand() % validCharsLength; | |
result[i] = validChars[charPosition]; | |
} | |
result[maxLength] = '\0'; | |
// set req.X-Country-Code header | |
VRT_SetHdr(sp, HDR_REQ, "\024X-Pagecache-Formkey:", result, vrt_magic_string_end); | |
return 0; | |
} | |
}C |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment