夜雪天狼
学习笔记
技术博文
转载备份
心灵鸡汤
目录
相同url根据nginx判断终端返回不同页面
发布者:caijw
阅读量:55371
发布时间:2019-04-15 14:28:56
# 前言 因业务需要,要根据nginx判断终端类型并返回响应的页面,且使用同一套url,在配置nginx的过程中,踩了一些坑方才配置完成,最后将本次配置的过程和处理方案记录下来 # 需求背景 1. 项目采用laravel+react的前后端分离结构,且为同一个项目,其中api目录为接口目录,由laravel处理,其他目录交由react处理 2. 前端分为移动端和pc端,因业务逻辑类似,故而没有分为两个项目,而是生成了两个页面pc.html和mobile.html,且在同一目录下 3. 移动端和pc端使用同一个地址,根据服务器端判断终端类型返回不同的页面 # 确定解决方案 服务器端可以写一个php入口文件,在判断终端类型从而加载不同的html文件,这是最简单的方案 但原本是静态资源文件,却多绕了一层php处理,影响性能 所以只得通过nginx判断终端,并返回相应的页面 于是我这个nginx半吊子只得踏上了采坑之路 # 说明 * 项目根目录为/www/public,下面有以下文件(夹) * statics:静态资源目录 * index.php:laravel的入口文件 * pc.html:pc端的入口文件 * mobile.html:移动端的入口文件 * 以下的配置文件只涉及路径及不同终端的处理,对于域名等其他无关配置均没有显示 > 内心独白:这个应该挺简单的吧,开搞开搞 # 正常的laravel+react配置 ``` root /www/public; location / { try_files $uri /index.html?$query_string; # react的入口文件 } location ~* ^\/(api|ms) { try_files $uri /index.php?$query_string; } ``` # 采坑第一次 —— 配置一 ``` 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里面吧 # 采坑第二次 —— 配置二 ``` 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 ```shell sudo nginx -s reload nginx: [emerg] "try_files" directive is not allowed here in nginx.conf:15 ``` 既然不能写try_files,那只能研究下配置一为啥不生效了 经过一番探索发现,nginx的if还是比较坑的,里面能写的东西有限,如果写了别的,可能导致不可预期的行为(参考[Nginx配置陷阱之万恶的if指令](https://xwsoul.com/posts/761?tdsourcetag=s_pctim_aiomsg "Nginx配置陷阱之万恶的if指令")) # 采坑第三次 既然if里面只能写rewrite和return,那要不就用rewrite? emmm...不行,用rewrite地址就变了 # 采坑第四次 如果要保证地址不变,try_files不行,那要不用反向代理吧,这个地址不变,而且肯定能解决问题 可是这个想法刚刚冒出来就被否了,为了保证效率,我连最简单的php中转都没用,咋能用同样损耗性能的反向代理呢 # 迈向曙光第一步 —— 配置四 又经过一番探索,找到了一个类似需求的可行配置,如下: ``` # 这个项目的情况是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/; } ``` 上述的配置适用于两个目录,而我的需求是两个文件,依样画葫芦,画一画吧 ``` 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也可以使用这种方式啊,就变成了如下的配置 # 雏形 —— 配置五 ``` 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干嘛,于是我进行了最后的尝试 # 最终方案 —— 配置六 ``` 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的注意事项 1. 使用`alias`时,目录名后面一定要加"/" 2. 使用`alias`标签的目录块中不能使用`rewrite`的`break` 3. `alias`在使用正则匹配时,必须捕捉要匹配的内容并在指定的内容处使用 4. `alias`只能位于`location`块中 ### 例子 ``` # 在这段配置下,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地址,谁知道。。。 这是坑爹的百度搜出来的结果:[错误之源](https://www.yduba.com/biancheng-5301527036.html "错误之源") 用法: rewrite \[正则\] \[替换\] 标志位 rewrite是实现URL重写的关键指令,根据regex(正则表达式)部分内容,重定向到replacement,结尾是flag标记 ### flag标记分为下面4仲: 1. last :本条规则匹配完成后,继续向下匹配新的location URI规则 2. break :本条规则匹配完成即终止,不再匹配后面的任何规则 3. redirect :返回302临时重定向,浏览器地址会显示跳转后的URL地址 4. permanent :返回301永久重定向,浏览器地址栏会显示跳转后的URL地址 通过上面的flag标记来看,只有设置了redirect或permanent地址才会改变 ### last和break的使用 区别上面已经说明,在本文中,有几个配置用到了这两个关键词 在配置五中,只能使用last,而不能使用break,是因为如果使用了break,则终止了后面的任何规则匹配,那么fuckurl就无法解析了 ``` 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将其终止 ``` 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; } ``` # 总结 1. 只能相信手册,如果是从非官方给出的解释,亲自证实后才能相信 2. 珍爱生命,远离百度 -separator-