Skip to content

Instantly share code, notes, and snippets.

@dleone81
Created January 3, 2016 16:33
Show Gist options
  • Save dleone81/6755d0d8efd3253079fe to your computer and use it in GitHub Desktop.
Save dleone81/6755d0d8efd3253079fe to your computer and use it in GitHub Desktop.
Adding ESI support to PHOENIX-PageCache
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