Varnish redirect based on browser language settings - redirect

I am using varnish 4 in front of apache. I need requests made to deutsh.de coming from headers with the preferred language es or ca (unless it also has de or en) to be redirected to spanish.es.
Could somebody provide me with the appropriate syntax?
Thank you

So I managed to put together something in the file used to start varnish:
sub vcl_recv {
if((req.http.Accept-Language !~ "de" || req.http.Accept-Language !~ "en") && (req.http.Accept-Language ~ "es" || req.http.Accept-Language ~ "ca" || req.http.Accept-Language ~ "eu"))
{
return(synth(301,"Moved Permanently"));
}
}
sub vcl_synth {
if(req.http.Accept-Language ~ "es" || req.http.Accept-Language ~ "ca" || req.http.Accept-Language ~ "eu")
{
set resp.http.Location = "http://spanish.es";
return (deliver);
}
}
...This appears to work

I have slightly extended the proposed solution with some regex that guarantees that we dont have german or english as a higher prioritised language configured in the accept-language header.
To explain the regex I think it would be good to keep in mind how such an Accept-Language header might look like: Accept-Language: de-DE,en-US,es
To consider the preferences of the users the used regex searches for the provided language but at the same time ensures that none of the other offered languages will be found before.
The latter is achieved somewhat cryptically with a negative look ahead expression "(^(?!de|en).)*" to ensure that neither de, nor en appears before the "es|ca|eu" entry.
^ # line beginning
.* # any character repeated any number of times, including 0
?! # negative look-ahead assertion
Additionally I have added a check if SSL is already used to achieve the language and SSL switch in one redirect.
With the return(synth(850, "Moved permanently")); you save one if clause in the vcl_synth which will reduce your config a lot especially when you have to do many of those language based redirects.
sub vcl_recv {
if (req.http.X-Forwarded-Proto !~ "(?i)https" && req.http.Accept-Language ~ "^((?!de|en).)*(es|ca|eu)" {
set req.http.x-redir = "https://spanish.es/" + req.url;
return(synth(850, "Moved permanently"));
}
}
sub vcl_synth {
if (resp.status == 850) {
set resp.http.Location = req.http.x-redir;
set resp.status = 301;
return (deliver);
}
}

Related

Nginx redirect with Lua based on browser language

I have a wordpress multisite with nginx, and I have been trying to find a way to redirect users based on their browser language.
Thanks to Mark and Joris I was able to redirect in most cases, but I have one problem.
Here are my situations and code for your information.
My situations
My multisite setup is in subdomains. My main site is in Korean and other two sites are in Japanese and English.
Obviously I want to redirect Japan users to Japanese site and international users to English site, and I think I figured this out.
But, if I want to go to the main Korean site from subdomain sites, I keep getting redirected back to jp.domain.com or en.domain.com. There would not be many use cases like this, but I think this should be possible.
Code
location = / {
default_type text/html;
rewrite_by_lua '
if ngx.var.cookie_lang == "ko" then
return
elseif ngx.var.cookie_lang == "ja" then
ngx.redirect("http://jp.domain.com/")
return
elseif ngx.var.cookie_lang == "en" then
ngx.redirect("http://en.domain.com/")
return
end
if ngx.var.http_accept_language then
for lang in (ngx.var.http_accept_language .. ","):gmatch("([^,]*),") do
if string.sub(lang, 0, 2) == "ko" then
ngx.header["Set-Cookie"] = "lang=ko; path=/"
return
elseif string.sub(lang, 0, 2) == "ja" then
ngx.header["Set-Cookie"] = "lang=ja; path=/"
ngx.redirect("http://jp.domain.com/")
return
end
end
end
ngx.header["Set-Cookie"] = "lang=en; path=/"
ngx.redirect("http://en.domain.com/")
';
}
location / {
try_files $uri $uri/ /index.php?$args;
rewrite_by_lua '
if ngx.var.arg_lang == "ko" then
ngx.header["Set-Cookie"] = "lang=ko; path=/"
elseif ngx.var.arg_lang == "ja" then
ngx.header["Set-Cookie"] = "lang=ja; path=/"
elseif ngx.var.arg_lang == "en" then
ngx.header["Set-Cookie"] = "lang=en; path=/"
end
';
}
Any help would be appreciated.

Using Parse::Lex, is there a way to return tokens only in certain states/conditions

Assuming that i need to tokenize and parse only multiline comments, how will i do that using Parse::Lex. When using flex-bison, the default action for any pattern in the rules section of the lex file used to be 'skip'.
%%
.* ;
%%
How to do this here ?
[EDIT] Well, i tried that, i'm still missing something - here is my code - and result. Where have i gone wrong ??
my simplified lex file:
use Parse::Lex;
use Regexp::Common;
use YParser;
my $lexer;
my #token = (
qw|esp:TA abcdefgh|,
qw(esp:REST .|\n),
);
Parse::Lex->trace;
Parse::Lex->exclusive('esp');
$lexer = Parse::Lex->new(#token);
$lexer->from(\*STDIN);
$lexer->skip(qr! [ \t]+ | $RE{balanced}{-begin=>'/*'}{-end=>'*/'} !xms);
$lexer->start('esp');
my $j = YParser->new();
$j->YYParse(yylex => \&lex);
sub lex {
my $token = $lexer->next;
return ('', undef) if $lexer->eoi;
if ($token->name eq 'TA' || $token->name eq 'REST') {
return ($token->name, {LINENO => $lexer->line, TEXT => $token->text});
}
}
my simplified grammar file
% token TA REST
%%
Program: Element
| Program Element
;
Element: TA
| REST
;
%%
Input file:
abcdefgh
/*sdf*/
Result:
perl lexfile.pl < inputfile
Trace is ON in class Parse::Lex
Can't call method "name" on an undefined value at qnlex.pl line 26, <STDIN> line 1.
Use the skip setting, shown here using Regexp::Common to help construct a regexp matching balanced pairs of comment delimiters. I've assumed /* */ as the comment delimiters, but they could be anything.
$lexer->skip(qr! [ \t]+ | $RE{balanced}{-begin=>'/*'}{-end=>'*/'} !xms);
The [ \t]+ alternative is left in place since that's the default.
Well, i figured this out :) Very simple - all i have to do is make the lex get the next token when encountering tokens i want to skip. Below is code to skip passing the token 'REST' to the parser.
sub lex {
my $token;
NEXTTOKEN:
$token = $lexer->next;
return ('', undef) if $lexer->eoi;
if ($token->name eq 'TA') {
return ($token->name, {LINENO => $lexer->line, TEXT => $token->text});
}
elsif ($token->name eq 'REST') {
goto NEXTTOKEN;
}
}

Perl 'if' statement

Right now, I have the following Perl code
my $tmpl1="download1_video.html"
if $file->{file_name}=~/\.(avi|divx|mkv|flv|mp4|wmv)$/i;
$tmpl1||="download1.html";
so it's checking to see if the file is a video, and if so it directs it to the certain page. Although I'm just wondering how I can add another if statement in there to check if the extension is .mp3, and if so direct it to download1_audio.html.
if ( $file->{file_name} =~ m/\.(avi|divx|mkv|flv|mp4|wmv)$/i ){
## Download video
}
elsif($file->{file_name} =~ m/\.(mp3)$/i){
## Download Audio
}
Is this what you needed ?
if ($file->{file_name} =~ /\.(avi|divx|mkv|flv|mp4|mp3|wmv)$/i )
{
if ($1 eq "mp3")
{
# mp3 stuff
}
elsif ($1 eq "mp4")
{
# mp4 stuff
}
else
{
# all other file types
}
}
else
{
# It didn't match
}
A fancier way would be to create a hash keyed by your file types in advance with the info you needed for your next page; the filename I guess?
my %pageHash = ( "mp3" => "mp3Page.html", "divx" => "divxPage.html", ... );
...
$file->{file_name} =~ /\.(.*)$/i;
if (exists $pageHash{$1})
{
$page = $pageHash{$1};
}
else
{
# unknown file extension
}
Having just been burnt by this, I must advise you against declaring a variable with a conditional modifier. If the condition does not hold true, it runs no part of the other clause, which means that you are not declaring $tmpl1, but since it's already passed strict, it allows you to assign to an undefined position in memory.
There is a safer way to do what your predecessor is doing here, that can yet illustrate a solution.
my $tmpl1
= $file->{file_name} =~ /\.(avi|divx|mkv|flv|mp4|wmv)$/i
? 'download1_video.html'
: $file->{file_name} =~ m/\.mp3$/i
? 'download1_audio.html'
: 'download1.html'
;
Thus,
$tmpl1 is always declared
$tmpl1 is always assigned a value

Language redirect or not

I have a website that has multiple languages. The way this is set up now is that it looks at the http accept language and redirect the user to the specific language, or uses a default language when none is found.
The problem that I am facing is that web crawlers can't index the root page, because it gives a 302 redirect. http://www.mydomain.com gets redirected to http://www.mydomain.com/nl/
The only way the website can be indexed is if I supply a sitemap for the whole website, including the languages. I have done that but I have not seen any indexed pages for weeks now.
So my question is: Will it be better to just have the website work in a default language.
To have the website in your own language you have to select the language when you are in the root website itself.
The problem that I am facing is that web crawlers can't index the root page
I haven't seen this problem before. Webcrawlers certainly follows 302 redirects. Any chance that you're (unawarely) blocking visitors without an Accept-Language header like webcrawlers?
So my question is: Will it be better to just have the website work in a default language. To have the website in your own language you have to select the language when you are in the root website itself.
I'd rather prefer the Accept-Language header and display the language which has the closest match with the in the header specified language(s) as per the HTTP 1.1 Specification. If none is specified, I'd display English as default language or at least the language which has the biggest coverage among the (expected) website audience.
I see in your question history that you're a PHP developer, so here's an useful snippet to determine the closest match based on the Accept-Language header as per the HTTP 1.1 specification:
function get_language($available_languages, $preferred_language = 'auto') {
preg_match_all('/([[:alpha:]]{1,8})(-([[:alpha:]|-]{1,8}))?(\s*;\s*q\s*=\s*(1\.0{0,3}|0\.\d{0,3}))?\s*(,|$)/i',
$preferred_language == 'auto' ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : $preferred_language, $languages, PREG_SET_ORDER);
$preferred_language = $available_languages[0]; // Set default for the case no match is found.
$best_qvalue = 0;
foreach ($languages as $language_items) {
$language_prefix = strtolower($language_items[1]);
$language = $language_prefix . (!empty($language_items[3]) ? '-' . strtolower($language_items[3]) : '');
$qvalue = !empty($language_items[5]) ? floatval($language_items[5]) : 1.0;
if (in_array($language, $available_languages) && ($qvalue > $best_qvalue)) {
$preferred_language = $language;
$best_qvalue = $qvalue;
} else if (in_array($language_prefix, $available_languages) && (($qvalue*0.9) > $best_qvalue)) {
$preferred_language = $language_prefix;
$best_qvalue = $qvalue * 0.9;
}
}
return $preferred_language;
}
(the above is actually a rewrite/finetune of an example found somewhere at php.net)
It can be used as follows:
$available_languages = array(
'en' => 'English',
'de' => 'Deutsch',
'nl' => 'Nederlands'
);
$requested_language = get_it_somehow_from_URL() ?: 'auto';
$current_language = get_language(array_keys($languages), $requested_language);
if ($requested_language != $current_language) {
// Unknown language.
header('Location: /' . $current_language . '/' . $requested_page);
exit;
}

How can I set the Cache-Control header for every response in Catalyst?

It seems that by default Catalyst does not output Cache-Control:, etc. headers. I know I can output them in a given controller method like this:
$c->response->headers->last_modified(time);
$c->response->headers->expires(time + $self->{cache_time});
$c->response->headers->header(cache_control => "public, max-age=$self->{cache_time}");
It'd get pretty painful doing that in each method, though! What I'd prefer is:
A default set of headers (expires now, last modified now, cache-control: no-cache, pragma: no-cache)
A way to, per-method, override the default.
Is there a good way to accomplish this?
derobert:
Excellent question. I covered exactly this in an article for the Catalyst advent calendar.
Basically you create a stash variable that defines your cache time for the given action, and then you process it in your Root end routine. See the article for all the details.
JayK
Update: Based on your response to my earlier suggestion, I decided to bite the bullet and look at the Catalyst docs. It seems to me, the place to do this is in:
sub end : Private {
my ( $self, $c ) = #_;
# handle errors etc.
if ( $c->res->body ) {
if ( "some condition" ) {
set_default_response_headers( $c->response->headers );
return;
}
else {
do_something_else();
return;
}
}
$c->forward( 'MyApp::View::TT' ); # render template
}
Earlier response: I do not use Catalyst, but couldn't you just write a sub for your application?
sub set_default_response_headers {
my ($h) = #_;
$h->last_modified(time);
$h->expires(time + $self->{cache_time});
$h->header(cache_control => "public, max-age=$self->{cache_time}");
return $h;
}
Call with set_default_response_headers( $c->response->headers ).