前言
因业务需要,要根据nginx判断终端类型并返回响应的页面,且使用同一套url,在配置nginx的过程中,踩了一些坑方才配置完成,最后将本次配置的过程和处理方案记录下来
需求背景
- 项目采用laravel+react的前后端分离结构,且为同一个项目,其中api目录为接口目录,由laravel处理,其他目录交由react处理
- 前端分为移动端和pc端,因业务逻辑类似,故而没有分为两个项目,而是生成了两个页面pc.html和mobile.html,且在同一目录下
- 移动端和pc端使用同一个地址,根据服务器端判断终端类型返回不同的页面
确定解决方案
服务器端可以写一个php入口文件,在判断终端类型从而加载不同的html文件,这是最简单的方案
但原本是静态资源文件,却多绕了一层php处理,影响性能
所以只得通过nginx判断终端,并返回相应的页面
于是我这个nginx半吊子只得踏上了采坑之路
说明
- 项目根目录为/www/public,下面有以下文件(夹)
- statics:静态资源目录
- index.php:laravel的入口文件
- pc.html:pc端的入口文件
- mobile.html:移动端的入口文件
- 以下的配置文件只涉及路径及不同终端的处理,对于域名等其他无关配置均没有显示
内心独白:这个应该挺简单的吧,开搞开搞
正常的laravel+react配置
1 2 3 4 5 6 7 |
root /www/public; location / { try_files $uri /index.html?$query_string; # react的入口文件 } location ~* ^\/(api|ms) { try_files $uri /index.php?$query_string; } |
采坑第一次 —— 配置一
1 2 3 4 5 6 7 8 9 10 11 |
root /www/public; location / { set $platform pc; if ( $http_user_agent ~ "(iPhone)|(Android)" ){ set $platform mobile; } try_files $uri /$platform.html?$query_string; } location ~* ^\/(api|ms) { try_files $uri /index.php?$query_string; } |
测试结果:
- 接口:没问题(淡定)
- pc端:没问题(微笑)
- 移动端:403(纳尼??)
难道,set在if里面没有生效,那干脆直接把try_files写在if里面吧
采坑第二次 —— 配置二
1 2 3 4 5 6 7 8 9 10 |
root /www/public; location / { if ( $http_user_agent ~ "(iPhone)|(Android)" ){ try_files $uri /mobile.html?$query_string; } try_files $uri /pc.html?$query_string; } location ~* ^\/(api|ms) { try_files $uri /index.php?$query_string; } |
重启Nginx,才发现if中不能写try_files
1 2 |
sudo nginx -s reload nginx: [emerg] "try_files" directive is not allowed here in nginx.conf:15 |
既然不能写try_files,那只能研究下配置一为啥不生效了
经过一番探索发现,nginx的if还是比较坑的,里面能写的东西有限,如果写了别的,可能导致不可预期的行为
采坑第三次
既然if里面只能写rewrite和return,那要不就用rewrite?
emmm…不行,用rewrite地址就变了
采坑第四次
如果要保证地址不变,try_files不行,那要不用反向代理吧,这个地址不变,而且肯定能解决问题
可是这个想法刚刚冒出来就被否了,为了保证效率,我连最简单的php中转都没用,咋能用同样损耗性能的反向代理呢
迈向曙光第一步 —— 配置四
又经过一番探索,找到了一个类似需求的可行配置,如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
# 这个项目的情况是pc目录是/home/www/,而移动的目录是:/home/www/test/mobile/ root /home/www/; location ~ ^/test(/.*) { set $way $1; if ( $http_user_agent ~ "(iPhone)|(Android)" ){ rewrite ^ fuckurl$way; } } location fuckurl/ { # 忽略名字,我想这是原作者和我共同的心声 internal; alias /home/www/test/mobile/; } |
上述的配置适用于两个目录,而我的需求是两个文件,依样画葫芦,画一画吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
root /www/public; location / { if ( $http_user_agent ~ "(iPhone)|(Android)" ){ rewrite ^ fuckurl/mobile.html?$query_string; } try_files $uri /pc.html?$query_string; } location fuckurl/ { internal; alias /www/public/; } location ~* ^\/(api|ms) { try_files $uri /index.php?$query_string; } |
测试后发现都木有问题
只不过这带来一个新问题,不是用rewrite地址就变了嘛?这个问题稍后再议
此时的配置已经满足了需求,只是还有些小瑕疵:
当使用pc访问mobile.html,出来的还是手机页面,但手机访问pc.html则没有问题,因为手机无论访问的地址是什么,都会转发到/mobile.html
那pc也可以使用这种方式啊,就变成了如下的配置
雏形 —— 配置五
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
root /www/public; location / { if ( $http_user_agent ~ "(iPhone)|(Android)" ){ # 这里使用last是为了保证如果进了if,则停止继续往下走 # 否则,无论是手机还是pc,都会显示pc # 但也只能使用last,而不能使用break,后面会详述 rewrite ^ fuckurl/mobile.html?$query_string last; } rewrite ^ fuckurl/pc.html?$query_string last; } location fuckurl/ { internal; alias /www/public/; } location ~* ^\/(api|ms) { try_files $uri /index.php?$query_string; } |
到了这一步基本完美了,但是,如果rewrite可以使url不变,那还要fuckurl干嘛,于是我进行了最后的尝试
最终方案 —— 配置六
1 2 3 4 5 6 7 8 9 10 11 |
root /www/public; location / { if ( $http_user_agent ~ "(iPhone)|(Android)" ){ # 这里只能使用break,后面会详述 rewrite ^ /mobile.html?$query_string break; } rewrite ^ /pc.html?$query_string break; } location ~* ^\/(api|ms) { try_files $uri /index.php?$query_string; } |
看到这个版本,我真是欲哭无泪,仰天长叹一声fuck
知识点
通过这次,又对nginx的配置熟悉了一些,本次配置用到了以下知识,将其记录一下
root和alias
虚拟服务器可拥有一个宿主目录和任意数量的其它目录,这些其它目录通常被称为虚拟目录。
nginx没有虚拟目录的说法,因为nginx本来就根据目录设计并工作的,如果要把nginx强硬插上一个虚拟目录
的说法,那只有alias
标签比较像
还有一个和alias
相似的标签root
,它们的区别如下:
- root:指定根目录,该根目录下要含有
locatoin
指定名称的同名目录 - alias:指定一个真实存在的目录,与
locatoin
指定名称不一定一致, 作用可以理解为linux的ln
,给location链接到一个目录
alias的注意事项
- 使用
alias
时,目录名后面一定要加”/“ - 使用
alias
标签的目录块中不能使用rewrite
的break
alias
在使用正则匹配时,必须捕捉要匹配的内容并在指定的内容处使用alias
只能位于location
块中
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 在这段配置下,http://test/abc/a.html 指定的是 /home/html/abc/a.html location /abc/ { alias /home/html/abc/; } # 上面这段配置亦可改成使用root标签(这样nginx就会去找/home/html/目录下的abc目录了,得到的结果是相同的) location /abc/ { root /home/html/; } # 但如果把alias的配置改成如下配置,http://test/abc/a.html 指向的是 /home/html/def/a.html location /abc/ { alias /home/html/def/; } # 这段配置就不能使用root直接配置了,如果非要配置,只有在 /home/html/下建立一个def->abc的软link(快捷方式)了 |
一般情况下,在server
中配置root
,在location
中配置alias
是一个好习惯
rewrite
本次闹出的乌龙坑,全因为对rewrite
认识不足,一直认为rewrite
会改变url地址,谁知道。。。
用法: rewrite [正则] [替换] 标志位
rewrite是实现URL重写的关键指令,根据regex(正则表达式)部分内容,重定向到replacement,结尾是flag标记
flag标记分为下面4仲:
- last :本条规则匹配完成后,继续向下匹配新的location URI规则
- break :本条规则匹配完成即终止,不再匹配后面的任何规则
- redirect :返回302临时重定向,浏览器地址会显示跳转后的URL地址
- permanent :返回301永久重定向,浏览器地址栏会显示跳转后的URL地址
通过上面的flag标记来看,只有设置了redirect或permanent地址才会改变
last和break的使用
区别上面已经说明,在本文中,有几个配置用到了这两个关键词
在配置五中,只能使用last,而不能使用break,是因为如果使用了break,则终止了后面的任何规则匹配,那么fuckurl就无法解析了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
root /www/public; location / { if ( $http_user_agent ~ "(iPhone)|(Android)" ){ # 这里使用last是为了保证如果进了if,则停止继续往下走 # 否则,无论是手机还是pc,都会显示pc # 但也只能使用last,而不能使用break,后面会详述 rewrite ^ fuckurl/mobile.html?$query_string last; } rewrite ^ fuckurl/pc.html?$query_string last; } location fuckurl/ { internal; alias /www/public/; } location ~* ^\/(api|ms) { try_files $uri /index.php?$query_string; } |
在配置六中,只能使用break,是因为如果使用last,会造成死循环,报500错误,所以必须使用break将其终止
1 2 3 4 5 6 7 8 9 10 11 |
root /www/public; location / { if ( $http_user_agent ~ "(iPhone)|(Android)" ){ # 这里只能使用break,后面会详述 rewrite ^ /mobile.html?$query_string break; } rewrite ^ /pc.html?$query_string break; } location ~* ^\/(api|ms) { try_files $uri /index.php?$query_string; } |
总结
- 只能相信手册,如果是从非官方给出的解释,亲自证实后才能相信
- 珍爱生命,远离百度
转自:https://www.caijw.com/p/9e6t34l2.html