如何根据客户端浏览器的语言在 nginx 中重写位置?
Posted
技术标签:
【中文标题】如何根据客户端浏览器的语言在 nginx 中重写位置?【英文标题】:How to rewrite location in nginx depending on the client-browser's language? 【发布时间】:2011-04-09 03:03:59 【问题描述】:例如: 我的浏览器接受语言是“uk,ru,en”。 当我请求位置 mysite.org 时,nginx 必须转发到 mysite.org/uk
【问题讨论】:
【参考方案1】:当您无法将 AcceptLanguageModule 模块添加到系统中时,您可以通过此设置管理 $language_suffix。
rewrite (.*) $1/$http_accept_language
更有弹性的方法是使用地图:
map $http_accept_language $lang
default en;
~es es;
~fr fr;
...
rewrite (.*) $1/$lang;
【讨论】:
其实~和表达式之间不应该有空格。 这需要 AcceptLanguageModule 吗? 不适合我,它总是把我带到英文页面,即使我将浏览器配置为仅限法语 这不需要 AcceptLanguageModule 此映射不支持首选项。要改进此映射,您可以将正则表达式从~es es;
更改为 ~*^es es;
,这将仅将语言映射到出现在 Accept-Language
标头上的第一种语言(假设浏览器首先提供首选语言,我认为非常频繁)【参考方案2】:
使用 AcceptLanguageModule 的缺点是您不能再依赖自动系统更新。每次 nginx 更新(甚至是安全更新),你都必须自己编译 Nginx。 第二个缺点是模块假定接受语言已经按质量值排序。 我更喜欢 Lua,因为它可以在基于 debian 的发行版中轻松安装:
apt-get install nginx-extras
我的同事 Fillipo 在 Lua 中制作了很棒的 nginx-http-accept-lang 脚本。它正确处理质量值并相应地重定向用户。 我已经为该脚本制作了small modification。它接受支持的语言作为输入参数,并根据 Accept-Language 标头返回最合格的语言。使用返回值,您可以做任何您想做的事情。可用于重写、设置 lang cookie ...
我仅将语言确定用于根路径(位置 = /)。并且用户 lang cookie 优先于浏览器。 我的 nginx 配置文件如下所示:
map $cookie_lang $pref_lang
default "";
~en en;
~sk sk;
server
listen 80 default_server;
root /usr/share/nginx/html;
index index.html index.htm;
# Make site accessible from http://localhost/
server_name localhost;
location = /
# $lang_sup holds comma separated languages supported by site
set $lang_sup "en,sk";
set_by_lua_file $lang /etc/nginx/lang.lua $lang_sup;
if ($pref_lang)
set $lang $pref_lang;
add_header Set-Cookie lang=$lang;
rewrite (.*) $scheme://$server_name/$lang$1;
location /
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
【讨论】:
这很好,但是为什么不对原始脚本进行 PR 呢? 受此解决方案的启发,我提出了自己的支持获取参数和 cookie 的解决方案。你可以在这里试试:github.com/mallocator/nginx-lua-lang【参考方案3】:我认为使用 nginx map $http_accept_language
不是一个好主意,因为
它不尊重质量价值(Accept-Language
标头中的q
)。
假设您有:
map $http_accept_language $lang
default en;
~en en;
~da da;
客户端会发送Accept-Language: da, en-gb;q=0.8, en;q=0.7
使用 nginx 映射将始终将 $lang
映射到 en
,因为它只是在标头字符串中查找。
但正确的映射将是$lang = da
(因为在这种情况下,Danisch 的质量值q=1
大于英语q=0.7
)
更多信息请参见 RFC:http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
【讨论】:
我想这是对@brian-coca 答案的评论?【参考方案4】:好的,我遇到了同样的问题,并且“滥用”Lua 以根据浏览器语言进行重定向。
# Use Lua for HTTP redirect so the site works
# without the proxy backend.
location = /
rewrite_by_lua '
for lang in (ngx.var.http_accept_language .. ","):gmatch("([^,]*),") do
if string.sub(lang, 0, 2) == "en" then
ngx.redirect("/en/index.html")
end
if string.sub(lang, 0, 2) == "nl" then
ngx.redirect("/nl/index.html")
end
if string.sub(lang, 0, 2) == "de" then
ngx.redirect("/de/index.html")
end
end
ngx.redirect("/en/index.html")
';
注意:NGINx 需要将 liblua 编译到它上面。对于 Debian/Ubuntu:
apt-get install nginx-extras
【讨论】:
是否也可以检查 url 是否以mysite.org
结尾?
如果 http_accept_language 为空,这将引发内部服务器错误,如果有人想使用它,请务必在 lua 脚本的第一行在ngx.var.http_accept_language
上添加对nil
的检查。 【参考方案5】:
我知道这是一个非常古老的线程,但我在尝试解决相同问题时发现了它。只是想分享我最终得到的解决方案。与上面发布的不同,Accept-Language 中好像提到了几种语言,它会在我们可以服务的语言中选择第一个提到的。
#
# Determine what language to redirect to
# this sets the value of $lang variable to the language depending on the contents of Accept-Language request header
# the regexp pattern automatically matches a known language that is not preceded by another known language
# If no known language is found, it uses some heuristics (like RU for (uk|be|ky|kk|ba|tt|uz|sr|mk|bg) languages)
#
map $http_accept_language $lang
default en;
"~*^((|,)\s*(?!(ru|es|fr|pt|en))\w+(-\w+)?(;q=[\d\.]+)?)*(|,)\s*en\b" en;
"~*^((|,)\s*(?!(ru|es|fr|pt|en))\w+(-\w+)?(;q=[\d\.]+)?)*(|,)\s*es\b" es;
"~*^((|,)\s*(?!(ru|es|fr|pt|en))\w+(-\w+)?(;q=[\d\.]+)?)*(|,)\s*ru\b" ru;
"~*^((|,)\s*(?!(ru|es|fr|pt|en))\w+(-\w+)?(;q=[\d\.]+)?)*(|,)\s*fr\b" fr;
"~*^((|,)\s*(?!(ru|es|fr|pt|en))\w+(-\w+)?(;q=[\d\.]+)?)*(|,)\s*pt\b" pt;
"~*(^|,)\s*(uk|be|ky|kk|ba|tt|uz|sr|mk|bg)\b" ru;
"~*(^|,)\s*(ca|gl)\b" es;
...
rewrite (.*) $1/$lang;
此解决方案的局限性在于它假定 Accept-Language 标头中的语言按其偏好顺序列出。通常这是正确的,但不是官方要求的。例如,如果标题是“Accept-Language: da, en-US;q=0.1, pt-BR;q=1”,则变量 $lang 将设置为“en”,因为它甚至位于“pt”之前虽然 pt 有更大的权重。
在没有外部脚本的情况下,在 nginx 中似乎不可能在考虑所有权重的情况下选择正确的语言。在所有实际情况下,该解决方案对我来说都足够好,并且不需要任何外部模块。
【讨论】:
> 这个解决方案在所有实际情况下对我来说都足够好,并且不需要任何外部模块。虽然是真的,但谁愿意与这种正则表达式地狱搏斗? @whaefelinger 好吧,我知道谁想要,我知道。 :-) 这里提到的所有好的解决方案都需要为 nginx 安装一些额外的东西。我觉得安装除了 nginx 之外的任何东西对于这样一个小任务来说都是多余的。所以我花了半个小时,想出了一个解决问题的正则表达式。我想为什么不分享它,也许它可以帮助那些不认为正则表达式是地狱的人? > [..] 谁不认为正则表达式是地狱? :-) 这不是我写的。我只是想象一个可怜的家伙必须支持大量的语言,然后出了点问题(除了你的解决方案假定降序)。 顺便说一句,添加 LUA 很容易并且得到很好的支持。 LUA 占用空间小,速度非常快。因此,LUA 似乎是一个不错的候选者。但是,标准 LUA 中的正则表达式支持很差。此外还需要第 3 方库。然后在 NGINX 中的集成可能会变得讨厌。但是,我知道 LUA 已被 javascript 正式取代。 @whaefelinger,是的,我同意用我的方法支持大量语言可能不是一个好主意。但是,对于我的 5 种语言,我将坚持使用正则表达式,而不是仅仅为此任务安装 LUA。【参考方案6】:简单的解决方案,没有 MapModule 和 AcceptLanguageModule :
if ( $http_accept_language ~ ^(..) )
set $lang $1;
set $args hl=$lang&$args;
请注意,“set $args hl=$lang&$args”在“hl”查询参数中设置所需的语言代码(例如“en”、“fr”、“es”等)。 当然,如果查询参数不适合,您可以在其他重写规则中使用 $lang。 示例:
location ~/my/dir/path/
rewrite ^/my/dir/path/ /my/dir/path/$1/ break;
proxy_pass http://upstream_server;
【讨论】:
【参考方案7】:上面的 Lua 示例很好,但如果浏览器不发送任何 Accept-Language 标头,则会失败并返回错误 500。
在上面加上这个:
if ngx.var.http_accept_language == nil then
ngx.redirect("/en/")
end
【讨论】:
【参考方案8】:您可以使用nginx_accept_language_module。 Nginx 必须重新编译,但它的工作量比集成 Lua 少。
Link to github
【讨论】:
【参考方案9】:除了上面不尊重语言偏好的@Marks 答案。这是将 Accept-Language Header
值解析为语言和偏好值的 LUA 代码块
-- need two LUA regex cause LUA's default regex is pretty broken
-- In my opinion a killer argument against using / supporting LUA
rx = "%s*([a-zA-Z-]+)%s*;%s*q%s*=%s*(%d*.?%d+)"
rx2 = "%s*([a-zA-Z-]+)%s*"
-- (arg .. ",") => concatenation operation
for chunk in (arg .. ","):gmatch("([^,]*),") do
lang, q = string.match(chunk, rx)
if (not lang) then
lang = string.match(chunk, rx2)
q = 1.0
end
print(string.format("lang=[%s] q=[%s]",lang, tonumber(q * 1.0)))
end
申请时,我得到:
$ lua demo.lua 'en-US , de , fr ; q = 0.1 , dk;q=1 '
lang=[en-US] q=[1.0]
lang=[de] q=[1.0]
lang=[fr] q=[0.1]
lang=[dk] q=[1.0]
$ lua demo.lua ' de'
lang=[de] q=[1.0]
$ lua demo.lua ' de;'
lang=[de] q=[1.0]
$ lua demo.lua ' de;q'
lang=[de] q=[1.0]
$ lua demo.lua ' de;q='
lang=[de] q=[1.0]
$ lua demo.lua ' de;q=0'
lang=[de] q=[0.0]
$ lua demo.lua ' de;q=0.1'
lang=[de] q=[0.1]
最终我使用的不是像下面这样的 LUA 脚本来重定向:
rx = "%s*([a-zA-Z-]+)%s*;%s*q%s*=%s*(%d*.?%d+)"
rx2 = "%s*([a-zA-Z-]+)%s*"
sup = de = 0, en = 0, dk = 0 -- supported languages
win = lang = "en", q = 0 -- default values / winner
for chunk in (arg[1] .. ","):gmatch("([^,]*),") do
lang, q = string.match(chunk, rx)
if (not lang) then
lang = string.match(chunk, rx2)
q = 1.0
end
lang = string.lower(lang)
-- handle only supported languages
if (sup[lang]) then
q = tonumber(q)
-- update winner table if a better match is found
if (win.q < q) then
win.q = q
win.lang = lang
end
end
end
-- which language pref?
print(string.format("winner: %s",win.lang))
这给出了:
$ lua test.lua 'en-US;q=.7 , de;q=0.9 , fr ; q = 0.1 , dk ; q = 1 '
winner: dk
【讨论】:
【参考方案10】:所以这是原始问题的编译示例(基于我的案例验证可与 nginx-1.1.x 一起使用):
map $http_accept_language $lang
default en;
~ru ru;
~uk uk;
server
server_name mysite.org;
# ...
rewrite (.*) http://mysite.org/$lang$1;
【讨论】:
我们应该在哪里写地图部分?在 http 部分内? 我已经引用了特定 nginx.conf 的顶层部分。以上是关于如何根据客户端浏览器的语言在 nginx 中重写位置?的主要内容,如果未能解决你的问题,请参考以下文章