使用 Nginx map 指令匹配 User Agent 自定义值

原文链接:https://blog.tanteng.me/2016/10/nginx-map-... ,原文内容会不断完善,以原文为准。

本文介绍有关 User-Agent 的知识,以及使用 Nginx map 指令配合正则表达式匹配 User Agent 自定义值,通过捕获 UA 自定义值,可以做很多事情,其中一个场景是:让一台测试机支持多个测试同时测试一个项目,原理就是匹配 UA 值,设置不同的 WEB 根目录。

关于 User Agent

User Agent 中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。

用 Firefox的 Firebug 工具可以看到每次请求的请求头信息都包含 User-Agent 字段,如图所示:

pic1

通过查看 nginx 的请求日志如 access.log,也可以查看到每次请求的 UA 信息,如果是 curl 命令方式请求,可以看到 UA 信息是 curl:

192.168.10.1 – – [10/Oct/2016:02:42:59 +0000] “GET /hello HTTP/1.1” 200 18 “-” “Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0 tanteng”
127.0.0.1 – – [10/Oct/2016:02:43:23 +0000] “GET /hello HTTP/1.1” 200 22 “-” “curl/7.35.0”

Firefox 插件:User Agent Switcher

这个插件可以管理和切换不同的 User-Agent,比如模拟不同的浏览器,或者新增自定义的 UA,可以在 UA 中带上自己的标识。

比如新增一个自己的 UA,并加上自己的标识,如图:

pic2

这样 Firefox 发出的请求头信息中 UA 就是这个修改过后的,访问一个本地 WEB 项目,通过 firebug 或查看 nginx 的 access.log 日志,都可以发现请求 UA 发生了变化。

pic4

Nginx 配置匹配 User-Agent

在 nginx 配置文件中,$http_user_agent 表示 UA 的值,比如浏览器默认 UA 值是:

Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0

现在新增了一个 UA,UA 值是在后面加一个空格和名字,如:

Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0 tanteng

通过 Firefox 的 User Agent Swicher 这个插件切换到新的 UA,再通过浏览器访问,UA 就是这个修改之后的。

那么,在 nginx 中如何匹配这个最后的名字呢?这里可以用到 nginx 的 map 指令配合正则表达式。

map 指令正则匹配

map 指令使用 ngx_http_map_module 模块提供的,ngx_http_map_module 模块可以创建变量,这些变量的值与另外的变量值相关联。允许分类或者同时映射多个值到多个不同值并储存到一个变量中,map 指令用来创建变量,但是仅在变量被接受的时候执行视图映射操作,对于处理没有引用变量的请求时,这个模块并没有性能上的缺失。

在 nginx 的 http 域中,增加以下代码:

map $http_user_agent $user {
    default            '';
    ~curl              curl;
    ~(?<name>[\S]+$)   $name;
}

这里正则表达式 [\S]+$ 即匹配最后一个非空白字符,? 表示要匹配的结果用 $name 表示。

这个 map 域的意思是,匹配对象是 $http_user_agent,即 UA,默认 $user 值是空字符串,如果是 curl 开头的,$user 值就是 curl,如果可以匹配到最后一个非空字符串,那么 $user 的值就是这个最后的字符串。

在 server 域中添加如下测试代码:

location /hello {
    echo http_user_agent:$http_user_agent;
    echo user:$user;
}

执行 curl 命令:curl www.tanteng.me/hello

返回结果:
http_user_agent:curl/7.35.0
user:curl

在浏览器中访问,输出结果是:

http_user_agent:Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0 tanteng
user:tanteng

这样就成功获取到 UA 后面增加的一个值。

Nginx 根据不同的 UA 设置网站根目录

通过 map 指令获取到 UA 自定义值后,可以做如下设置,让不同的测试人员对应不同的网站根目录:

location ~ \.php$ {
    try_files      $uri =404;
    root           /usr/share/nginx/html/$user/public;
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include        fastcgi_params;
}

这里 root 行,网站目录可以用 $user 变量来表示。比如测试人员 ruike,他修改自己的 UA 后面加上 ruike,那么访问的网站根目录是 /usr/share/nginx/html/ruike/public,这样不同的测试人员在自己的各自目录拉取代码,互不影响,支持同时进行测试。

测试一下效果,把 public 下的 index.php 文件前面用 echo ‘wo shi ruike.’;exit; 输出。

切换到 ruike 这个 UA,访问网站,显示的即是以上内容,切换回 tanteng 这个 UA,显示的是正常的网站内容,这就达到了通过 UA 自定义值设置不同的网站根目录的效果,也就支持了一台测试机支持不同的测试人员同时进行测试的需求。

本帖已被设为精华帖!
本帖由 Summer 于 7年前 加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 20

这个测试的思路很独特! :+1:

我们是多搞几个测试的子域名(域名是泛解析到服务器),让 nginx 解析到不同的目录来实现。:smile:

7年前 评论

@纸牌屋弗兰克

比如使用的是 dnspod, 将域名 mytest.com 的 * 指向服务器。这样, 任何 mytest.com 的二级域名都会访问该服务器。接下来就是设置 nginx了, 配置多站点的 server_name 。比如 test1.mytest.com 指向一个目录, test2.mytest.com 指向一个目录。

如果是本地服务器的话,让测试人员将对应的测试域名修改 hosts 指向服务器, nginx 配置同上。

7年前 评论

@zhuzhichao 明白了,但是这样不能保持一致性,测试的时候是用的多个二级域名,正式环境并不是这样的。

7年前 评论

不太清楚你说的一致性是什么意思。

我们这边多个测试域名,都在不同的分支,但是在同一台服务器,测试完成的分支合并到master然后推到正式环境,没有什么问题的。一直这样做的 :smile:

7年前 评论

@zhuzhichao 比如一个网站www.domain.com,cart.domain.com,user.domain.com,你这样做的话就不行。

就算是一个域名,你可以用多个二级域名指向不同的目录去测试,我说的一致性,是测试的时候域名都和线上不一样,这就不一致。

好的测试环境,应该和线上尽可能都一样。

7年前 评论

@纸牌屋弗兰克 明白你的意思了。你说的这些我们会指派一些类似的,例如: www.domain.io,cart.domain.io,user.domain.io。 什么类型的域名随意。

你所说的 好的测试环境,应该和线上尽可能都一样 ,我认同这个观点,运行环境确实需尽量要一样,不仅是测试,还有开发,所以有 homestead 。

但是域名如果也需要一样,就不方便了,因为需要来回切换 hosts 或者 DNS 才能满足随时切换域名所对应生产和测试,这样的话,开发的时候需要看线上的,测试也需要看线上的,就挺麻烦的了,并且这东西还有缓存,一不注意就搞错了。很早之前用过你这类似的方式,很容易混淆线上和测试,测试人员和开发人员搞错,把线上重要数据当成测试。

还有,访问的是同一个域名,像你这样说的通过 User agent 方式,只能是测试和生产在同一台服务器了,起码是 nginx 要在同一台服务器。

我认为一个好的程序,域名是随时可以配置的。例如 Laravel Valet 默认就是 .dev 结尾的就是开发域名。

如上所说,我们的测试网站的域名 可能还有 dev-www.domain.com dev-cart.domain.com dev-user.domain.com 等等,方式很多。任何域名都可以使用,只需要在相应的配置文件稍加调整即可。

当然,如果说你在程序内各个地方将域名写死了的话,那么就如你所说,必须要域名一致了,但是带来了更多问题,比如说,原来使用 360buy 的京东就没办法很容易的换成 jd.com 了。

7年前 评论

@zhuzhichao 你说的 hosts 切换浏览器都有好用的插件,很方便切换到本地开发、测试、生产环境,另外我不建议域名不一样的做法,比如用 dev,这样做有个问题,我举个例子:

比如我们有个功能,在页面中通过 iframe 嵌入一个会员信息页,这个页面的登录态或者其他一些内容需要使用线上的,如果在开发环境采用 dev 形式域名,这个页面域名就跟线上域名不一样,就存在 session 不能共享的问题,开发的时候效果就出不来,必须到线上才正常。

所以域名也要保持一直比较好,你无法知道哪里的差异就会导致问题,特别是需求复杂,环境无法都统一的时候。

7年前 评论

@纸牌屋弗兰克 不清楚你们那边的开发和测试环境。

单是有一定规模的WEB项目应该是这样的(可能更复杂):

DEV 本地开发环境

CI 内网环境

ALPHA 公网测试环境

PROD 公网生产环境

域名都一样的话,你不觉得很混乱吗?

像你所说的偶尔需要线上接口进行登录的情况,可以使用同样的域名,但是二级域名不同,或者搞3级域名来解决,保证在同一个 domain.com 下即可,本地可以可以使用改 hosts my-user.domain.com 指向开发环境。内网就是 ci-user.domain.com,公网测试就是 alpha-user.domain.com,生成就是 user.domain.com 了。

域名的差异如果影响到了程序开发,那么就要看看是否自己的程序或者所依赖的程序不够友好。

7年前 评论

@纸牌屋弗兰克

甚至可以在修改经理的 hosts ,让 zzc-user.domain.com 指向内网我的 IP,我这边默认开发环境就是 zzc-user.domain.com 。我这边先做了个 demo ,可以随时让经理看,经理指出需要调整的,马上就可以看到效果。

不瞒你说,我最初工作的两年就是同个域名切换 hosts 。再好的 hosts 切换工具也有时候也会抽风。后来再也不那样做啦 :smile: 整个人好多了,再也不用给谁谁说切换域名 hosts 到这里到那里的,现在是一次添加,再无纠纷。

7年前 评论

@zhuzhichao 这个具体场景还得具体说吧,同一个域名你很在意切换不生效的问题,而我觉得连不同的环境得输入不同的域名更麻烦。

7年前 评论

@纸牌屋弗兰克
再说一点哈,别觉得我啰嗦。:smile:

APP 的测试也需要测试 API 域名或者偶尔直接输出网页需要非 api 域名,可以给他们不同的域名,便于切换生产和开发,如果是同个域名。。他们要哭了 :sweat_smile:

7年前 评论

@纸牌屋弗兰克

同个域名不生效问题还好。

其实上面说的比较清楚了,环境比较多,如果同个域名傻傻分不清楚,这是硬伤。不同环境不同域名多简洁明了,连切换都不用了,同个域名一会儿是这个环境一会儿是那个环境,更麻烦吧~~!

7年前 评论

@纸牌屋弗兰克 只是和你分享我的经验以及我所遇见的开发团队和项目的经验。跟我方便不方便真的没关系 :joy: (逃

7年前 评论

@zhuzhichao 不同的公司流程和规范是不一样的,这个好像也没有一个统一的标准,也要看具体情况而定,我也只是分享我所遇见的开发团队和项目的经验,无意争论。

7年前 评论

@纸牌屋弗兰克 应该定义为讨论。:kissing_smiling_eyes: (逃

7年前 评论
Summer

哈哈,原来亮点是在讨论区里

7年前 评论

讨论区的内容好精彩,确实为这个烦恼过

7年前 评论

这个需要学习下!

6年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!