forked from cosimo/varnish-accept-language
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 50e55ac
Showing
19 changed files
with
1,023 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
accept-language | ||
accept-language.vcl |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 !! | ||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
*/ |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
|
Oops, something went wrong.