Skip to content

Commit

Permalink
Initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
cosimo committed Jan 21, 2010
0 parents commit 50e55ac
Show file tree
Hide file tree
Showing 19 changed files with 1,023 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
accept-language
accept-language.vcl
19 changes: 19 additions & 0 deletions Makefile
@@ -0,0 +1,19 @@
# Language preferences
DEFAULT_LANGUAGE=en
SUPPORTED_LANGUAGES=en es it ja ru zh-cn

CC=cc
DEBUG=
#DEBUG=-g3

all: accept-language accept-language.vcl

accept-language: accept-language.c
$(CC) -Wall -pedantic $(DEBUG) -o accept-language accept-language.c

accept-language.vcl: Makefile accept-language.c gen_vcl.pl
./gen_vcl.pl $(DEFAULT_LANGUAGE) $(SUPPORTED_LANGUAGES) > accept-language.vcl

test:
prove -I./t -v ./t

67 changes: 67 additions & 0 deletions README
@@ -0,0 +1,67 @@
Varnish Accept-Language VCL
===========================

Last updated: 21/01/2010
Cosimo Streppone <cosimo@opera.com>
Opera Software ASA

Here you will find a VCL config file for Varnish (http://varnish-cache.org)
This VCL allows you to normalize and filter all the incoming requests
Accept-Language headers and reduce them to just the languages your site supports.

*** WARNING ***
This VCL consists of C code. Your Varnish might explode. YMMV.
Don't use it in production if you don't know what you're doing.
We are using it in production, but we don't know what we're doing.

Why would you want this?
------------------------
Your site supports English and Japanese languages.
Your client browsers will send every possible Accept-Language header on Earth.
If you enable "Vary: Accept-Language" on Varnish or on your backends,
the cache hit ratio will rapidly drop, because of the huge variations
in Accept-Language contents.

With this VCL, the Accept-Language header will be "rewritten" to just
"en" or "ja", depending on your client settings. If no match occurs,
you can select a default language.

The rewritten header is "X-Varnish-Accept-Language".
You can choose to put this header back in "Accept-Language" if you wish.
In this way, the normalization will be completely transparent.

Requirements
------------

- gcc, make
- a recent perl, with `prove'
- File::Slurp CPAN module

Instructions
-------------

1) Configure the list of languages your site supports
and the default fallback in the Makefile, *NOT* in the C code.

2) Run 'make && make test'
You should see "All tests successful" at the end of the execution

3) Install the generated accept-language.vcl in /etc/varnish/

4) Add the following include directives at the top of your main VCL file
(default.vcl or the name you're using).

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

5) Inside your vcl_recv(), add the following line:

include "/etc/varnish/accept-language.vcl"

6) Restart Varnish

7) Cross your fingers

8) Profit !!

126 changes: 126 additions & 0 deletions accept-language.c
@@ -0,0 +1,126 @@
/*
* Accept-language header normalization
*
* Cosimo, 21/01/2010
*
*/

#include <string.h>
#include <stdio.h>
#include <stdlib.h> /* qsort */

#define DEFAULT_LANGUAGE "en"
#define SUPPORTED_LANGUAGES ":bg:cs:da:en:fi:fy:hu:it:ja:no:pl:ru:tr:uk:xx-lol:vn:zh-cn:"

#define vcl_string char
#define LANG_LIST_SIZE 10
#define LANG_MAXLEN 10
#define RETURN_LANG(x) { \
strncpy(lang, x, LANG_MAXLEN); \
return; \
}
#define RETURN_DEFAULT_LANG RETURN_LANG(DEFAULT_LANGUAGE)
#define PUSH_LANG(x,y) { \
pl[curr_lang].lang = x; \
pl[curr_lang].q = y; \
curr_lang++; \
}

struct lang_list {
vcl_string *lang;
float q;
};

/* Checks if a given language is in the static list of the ones we support */
int is_supported(vcl_string *lang) {
vcl_string *supported_languages = SUPPORTED_LANGUAGES;
vcl_string match_str[LANG_MAXLEN + 3] = ""; /* :, :, \0 = 3 */
int is_supported = 0;

/* Search ":<lang>:" in supported languages string */
strncpy(match_str, ":", 1);
strncat(match_str, lang, LANG_MAXLEN);
strncat(match_str, ":\0", 2);

if (strstr(supported_languages, match_str)) {
is_supported = 1;
}

return is_supported;
}

/* Used by qsort() below */
int sort_by_q(const void *x, const void *y) {
struct lang_list *a = (struct lang_list *)x;
struct lang_list *b = (struct lang_list *)y;
if (a->q > b->q) return -1;
if (a->q < b->q) return 1;
return 0;
}

/* Reads Accept-Language, parses it, and finds the first match
among the supported languages. In case of no match,
returns the default language.
*/
void select_language(const vcl_string *incoming_header, char *lang) {

struct lang_list pl[LANG_LIST_SIZE];
vcl_string *lang_tok = NULL;
vcl_string *header;
vcl_string *pos = NULL;
vcl_string *q_spec = NULL;
unsigned int curr_lang = 0, i = 0;
float q;

/* Empty string, return default language immediately */
if (! incoming_header || (0 == strcmp(incoming_header, ""))) {
RETURN_DEFAULT_LANG;
}

/* Tokenize Accept-Language */
header = (vcl_string *) incoming_header;

while ((lang_tok = strtok_r(header, " ,", &pos))) {
q = 1.0;
if ((q_spec = strstr(lang_tok, ";q="))) {
/* Truncate language name before ';' */
*q_spec = '\0';
/* Get q value */
sscanf(q_spec + 3, "%f", &q);
}
/* Wildcard language '*' should be last in list */
if ((*lang_tok) == '*') q = 0.0;
/* Push in the prioritized list */
PUSH_LANG(lang_tok, q);
/* For strtok_r() to proceed from where it left off */
header = NULL;
/* Break out if stored max no. of languages */
if (curr_lang >= LANG_MAXLEN) break;
}

/* Sort by priority */
qsort(pl, curr_lang, sizeof(struct lang_list), &sort_by_q);

/* Match with supported languages */
for (i = 0; i < curr_lang; i++) {
if (! is_supported(pl[i].lang)) continue;
RETURN_LANG(pl[i].lang);
}

RETURN_DEFAULT_LANG;
}

int main(int argc, char **argv) {
vcl_string lang[LANG_MAXLEN] = "";
if (argc != 2 || ! argv[1]) {
strncpy(lang, "en", 2);
}
else {
select_language(argv[1], lang);
}
printf("%s\n", lang);
return 0;
}

/* vim: syn=c ts=4 et sts=4 sw=4 tw=0
*/
134 changes: 134 additions & 0 deletions examples/accept-language.vcl
@@ -0,0 +1,134 @@
C{

/* ------------------------------------------------------------------ */
/* THIS FILE IS AUTOMATICALLY GENERATED BY ./gen_vcl.pl. DO NOT EDIT. */

/*
* Accept-language header normalization
*
* Cosimo, 21/01/2010
*
*/


#define DEFAULT_LANGUAGE "en"
#define SUPPORTED_LANGUAGES ":en:ja:"

#define vcl_string char
#define LANG_LIST_SIZE 10
#define LANG_MAXLEN 10
#define RETURN_LANG(x) { \
strncpy(lang, x, LANG_MAXLEN); \
return; \
}
#define RETURN_DEFAULT_LANG RETURN_LANG(DEFAULT_LANGUAGE)
#define PUSH_LANG(x,y) { \
pl[curr_lang].lang = x; \
pl[curr_lang].q = y; \
curr_lang++; \
}

struct lang_list {
vcl_string *lang;
float q;
};

/* Checks if a given language is in the static list of the ones we support */
int is_supported(vcl_string *lang) {
vcl_string *supported_languages = SUPPORTED_LANGUAGES;
vcl_string match_str[LANG_MAXLEN + 3] = ""; /* :, :, \0 = 3 */
int is_supported = 0;

/* Search ":<lang>:" in supported languages string */
strncpy(match_str, ":", 1);
strncat(match_str, lang, LANG_MAXLEN);
strncat(match_str, ":\0", 2);

if (strstr(supported_languages, match_str)) {
is_supported = 1;
}

return is_supported;
}

/* Used by qsort() below */
int sort_by_q(const void *x, const void *y) {
struct lang_list *a = (struct lang_list *)x;
struct lang_list *b = (struct lang_list *)y;
if (a->q > b->q) return -1;
if (a->q < b->q) return 1;
return 0;
}

/* Reads Accept-Language, parses it, and finds the first match
among the supported languages. In case of no match,
returns the default language.
*/
void select_language(const vcl_string *incoming_header, char *lang) {

struct lang_list pl[LANG_LIST_SIZE];
vcl_string *lang_tok = NULL;
vcl_string *header;
vcl_string *pos = NULL;
vcl_string *q_spec = NULL;
unsigned int curr_lang = 0, i = 0;
float q;

/* Empty string, return default language immediately */
if (! incoming_header || (0 == strcmp(incoming_header, ""))) {
RETURN_DEFAULT_LANG;
}

/* Tokenize Accept-Language */
header = (vcl_string *) incoming_header;

while ((lang_tok = strtok_r(header, " ,", &pos))) {
q = 1.0;
if ((q_spec = strstr(lang_tok, ";q="))) {
/* Truncate language name before ';' */
*q_spec = '\0';
/* Get q value */
sscanf(q_spec + 3, "%f", &q);
}
/* Wildcard language '*' should be last in list */
if ((*lang_tok) == '*') q = 0.0;
/* Push in the prioritized list */
PUSH_LANG(lang_tok, q);
/* For strtok_r() to proceed from where it left off */
header = NULL;
/* Break out if stored max no. of languages */
if (curr_lang >= LANG_MAXLEN) break;
}

/* Sort by priority */
qsort(pl, curr_lang, sizeof(struct lang_list), &sort_by_q);

/* Match with supported languages */
for (i = 0; i < curr_lang; i++) {
if (! is_supported(pl[i].lang)) continue;
RETURN_LANG(pl[i].lang);
}

RETURN_DEFAULT_LANG;
}



/* Get Accept-Language header from client */
incoming_header = VRT_GetHdr(sp, HDR_REQ, "\020Accept-Language:");
/* syslog(LOG_INFO, "accept-language.vcl: incoming header \"%s\"", incoming_header); */

/* Normalize and filter out by list of supported languages */
select_language(incoming_header, lang);

/* Set another header back, so it can be consumed */
VRT_SetHdr(sp, HDR_REQ, "\032X-Varnish-Accept-Language:", lang);
/* syslog(LOG_INFO, "accept-language.vcl: selected lang \"%s\"", lang); */


/* vim: syn=c ts=4 et sts=4 sw=4 tw=0
*/

/* THIS FILE IS AUTOMATICALLY GENERATED BY ./gen_vcl.pl. DO NOT EDIT. */
/* ------------------------------------------------------------------ */
}C
39 changes: 39 additions & 0 deletions examples/sample.vcl
@@ -0,0 +1,39 @@
# Sample VCL file

# For the accept-language.vcl "plugin" to work, we
# need these includes here. I couldn't manage to get them
# working in accept-language.vcl

C{
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
}C

# Everything proceeds as normal
sub vcl_recv {

# ...

include "/etc/varnish/accept-language.vcl";

# ...
# lookup;

pass;
}

sub vcl_fetch {

# ...

# Store different versions of the resource by the
# content of the Accept-Language header
set obj.http.Vary = "Accept-Language";

# ...
# deliver;

pass;
}

0 comments on commit 50e55ac

Please sign in to comment.