由 nginx echo 命令引发的探索

起因

最初在 nginx.conf 中调试时,当时希望 echo 出对应的变量值,并没有成功。起初认为是 nginx 安装时,并没有安装 echo 模块,然而事后发现实际用的是 openresty,openresty 在安装的时候,默认编译了 echo 模块,然而同样没有 echo 出对应的值,没有响应。特别是最近在 rewrite 命令之前希望 echo 出修改前的 uri 时,没有 echo 出来,然而在 rewrite 命令之后却 echo 出来了,于是乎更加疑惑。带着这些表面现象进行了深入的学习。

nginx.conf 是声明性

查找资料时,看到章亦春编写的 nginx 配置指令的执行顺序中提到的 "Nginx 的作者 Igor Sysoev 在公开场合曾不止一次地强调,Nginx 配置文件所使用的语言本质上是“声明性的”,而非“过程性的”( procedural)",顿时如醍醐灌顶。nginx 并不是按过程式执行的,执行的顺序并不是按 nginx.conf 的编写顺序。

nginx 主要执行阶段

nginx 的实际执行是阶段的,nginx 处理请求一共可划分为 11 个阶段,其中主要的是 rewrite 、access、content 三个阶段。其中 nginx 的不同模块分属不同阶段。各模块常见命令如下:
rewrite : ngx_rewrite 模块的 set、rewrite;ngx_set_misc 模块的 set_unescape_uri(可和nginx_rewrite 混合执行);ngx_lua 模块的 set_by_lua;ngx_headers_more 模块的 more_set_input_headers (总是 rewrite 阶段末尾);ngx_lua 模块的 rewrite_by_lua (rewrite 阶段末尾);
access : ngx_access 模块的 deny ;ngx_access 模块的 alllow(和 deny 命令一起时,谁在前就先执行谁,并跳过后者) ;ngx_lua 的 access_by_lua (access 阶段末尾)
content : ngx_echo 模块的 echo;ngx_lua 模块的 content_by_lua;ngx_proxy 模块的 proxy_pass;
执行顺序主要四点:

  1. 执行顺序为 rewrite > access > content ;
  2. 同一阶段的同一模块中的不同命令间的执行顺序是按书写顺序;
  3. 同一阶段大多数不同模块间的命令执行顺序是不确定的;
  4. 同一阶段部分不同模块间的命令也可按顺序执行,如 ngx_set_misc 模块和 ngx_rewrite 模块。
location ~ ^/(admin|api) {
     
    set $auth_uri "http://127.0.0.1:8901/auth/verify";                        # rewrite 阶段
    access_by_lua_file  $document_root/openresty/lua/authorization.lua;     # access 阶段
 
    # echo "original uri";                                                    # content 阶段,无法 echo 出来。
    rewrite  ^/(.*)$ /public/index.php break;                               # rewrite 阶段
    fastcgi_pass   127.0.0.1:9000;                                          # content 阶段
    fastcgi_index  index.php;                                               # contetn 阶段
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;     # contetn 阶段
 
    echo "after rewrite uri";                                             # content 阶段,能够 echo 出来,但 ngx_http_fastcgi_module 模块的命令没有执行。
    include        fastcgi_params;                                         
}

从上诉注释的标明阶段可以看到,先执行的的是 rewrite 阶段的 set 和 rewrite,然后是 access 阶段的 access_by_lua_file,再次是 content 阶段的 fastcgi_pass 、fastcgi_index 、fastcgi_param。set > rewrite > access_by_lua_file > fastcgi_pass > fastcgi_index > fastcgi_param 。
因此将 ngx_echo 模块的 echo 命令放置在 rewrite 命令之前,并不代表在 rewrite 命令之前执行。fastcgi_past 和 echo 都属于 content 阶段,但分属不同模块,执行顺序不确定。分析本文最开始出现的情况。

location ~ ^/(admin|api) {
     
    set $auth_uri "http://127.0.0.1:8901/auth/verify";                        # rewrite 阶段
    access_by_lua_file  $document_root/openresty/lua/authorization.lua;     # access 阶段
 
    echo "original uri";                                                 
    rewrite  ^/(.*)$ /public/index.php break;                               # rewrite 阶段
    fastcgi_pass   127.0.0.1:9000;                                          # content 阶段
    fastcgi_index  index.php;                                               # contetn 阶段
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;     # contetn 阶段
 
    echo "after rewrite uri";                                            
    include        fastcgi_params;                                         
}

首先,上述写法其目的是希望在 rewrite 命令前后,将 uri 打印出来,查看 rewrite 对 uri 的修改。上述两个错误:

  1. echo 命令属于 ngx_echo 模块,其执行阶段为 content,无法在 rewrite 命令之前 echo 出修改前的 uri;
  2. fastcgi_* 命令属于 ngx_http_fastcgi 模块,与 ngx_echo 为同 content 阶段执行的不同模块,执行顺序不确定。

上述写法在实际测试中 ngx_htttp_fastcgi 模块占优势,执行了。而当将 rewrite 之前的 echo 命令注释掉之后,ngx_echo 模块占优势,echo 出了修改后的 uri。上述两个模块,书写顺序在前的反而执行时不占优势,反而是后者成功执行,这不知道是不是规律,还有待验证。

rewrite 模块

rewrite 命令的功能为安装相关的正则表达式与字符串修改 uri。其语法为:
rewrite regex replacement flag

  1. regex 为正则表达式,可使用括号捕获,后续可根据位置应用捕获变量;
  2. replacement 为修改后的 uri;
  3. flag 为尾部标记。

尾部标记 flag 可由如下值:

  1. last —— 停止处理重写模块指令,搜索 location 与修改后的 uri 匹配;
  2. break —— 完成重写指令;
  3. redirect —— 返回 302 临时重定向;
  4. permanent —— 返回 301 永久重定向。

网上相关资料推荐利用 try_files 替换 rewrite 命令。
其语法为:
try_files file ... uri 或 try_files file ... = code
按顺序检查文件是否存在,返回第一个找到的文件。结尾的斜线表示为文件夹 —— $uri/。如果所有的文件都找不到,会进行一个内部重定向到最后一个参数。务必确认只有最后一个参数可以引起一个内部重定向,之前的参数只设置内部URI的指向。 最后一个参数是回退URI且必须存在,否则将会出现内部500错误。命名的location也可以使用在最后一个参数中。与rewrite指令不同,如果回退URI不是命名的location那么$args不会自动保留,如果你想保留$args,必须明确声明。
try_files $uri $uri/ /index.php?q=$uri&$args;
何为命令的 location,参照下例:

location / {
    try_files $uri $uri/ @drupal;
}
 
location @drupal {
    ...
}

上例中 @drupal 就是命令的 location。
下面通过 nginx 和 laravel 的 结合比较 rewrite 和 try_files 的写法。
rewrite:

server {
    ...
    root /path/to/laravel;
    ...
    location / {
        rewrite  ^/(.*)$ /public/index.php break;
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;                                         
    }
...
}

try_files:

server {
    ...
    root /path/to/laravel;
    ...
    # 修改为 Laravel 转发规则
    location / {
        try_files $uri $uri/ /public/index.php?$query_string;
    }
 
    # PHP 支持
    location ~ \.php$ {
        try_files $uri /index.php =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php5-fpm.sock;               # 可以进行 http 的转发
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

从书写上来看,rewrite 命令视乎更简洁。

nginx 中 fastcgi 的使用

简要介绍几个概念:

  1. CGI 协议:CGI就是规定 Web Server 与相应解析器间通信时要传哪些数据、以什么样的格式传递给后方来处理相应请求的协议;
  2. FastCGI 协议:Fastcgi 是用来提高 CGI 程序性能的的一套实现手段。Fastcgi 会首先先启一个master,解析配置文件,初始化执行环境,然后再启动多个worker,动态管理 worker 的个数。worker 用于处理请求。
  3. FastCGI 进程管理器:实现了 FastCGI 的程序;
  4. CGI 程序:支持 CGI 协议的应用程序;
  5. Web Server:Web 服务器。

Web server 只是内容的分发者。对于静态文件, web server 在文件系统中直接寻找,发送给浏览器,而对于非静态文件,web server 寻找相应的解析器。对于非静态文件,处理过程如下:

fastcgi.png

以 nginx 处理 php文件举例,上图的关系变为:

php-fpm.png

在 fastcgi_params 中我们可以看到传递的参数 fastcgi_param REQUEST_URI $request_uri。这个参数是用于告诉后方处理程序,这个请求的 uri 是多少。在上述 nginx.conf 中,利用 rewrite 命令改写 uri,其主要目的是给 $fastcgi_script_name 赋值。$fastcgi_script_name 默认是和 $uri 是一个值,修改 $uri 目的是定位至后方服务框架的入口文件进行处理。然而实际传递给后方程序的 uri 并没有修改,因为 $request_uri 是一个不可修改的文件。当希望自定义传递给后方程序的 uri 时,就得修改对应的语句为 fastcgi_param REQUEST_URI /you/want/uri;。
参考:深入理解 FastCGI 协议以及在 PHP 中的实现

location 的执行顺序

语法:location [=||*|^~] /uri/ { … }

模式 含义
location = /uri = 表示精确匹配,只有完全匹配上才能生效
location ^~ /uri ^~ 开头对URL路径进行前缀匹配,并且在正则之前。
location ~ pattern 开头表示区分大小写的正则匹配
location ~* pattern 开头表示不区分大小写的正则匹配
location /uri 不带任何修饰符,也表示前缀匹配,但是在正则匹配之后
location / 通用匹配,任何未匹配到其它location的请求都会匹配到,相当于switch中的default

前缀匹配时,nginx 不对 url 做编码,因此请求 /static/20%/aa ,可以被规则 ^~ /static/ /aa 匹配到。(注意是空格)
多个 location 匹配顺序:
=(精确) > ^~(前缀) > ~(区分大小写正则) > ~*(不区分大小写正则) > /uri (前缀)> /(通用);
当有匹配成功时,停止匹配,按当前匹配规则处理请求。
【注意】:前缀匹配,如果有包含关系时,按最大匹配原则进行匹配。location /dir01 与 location /dir01/dir02,如有请求 http://localhost/dir01/dir02/file 将最终匹配到 location /dir01/dir02。
正则匹配按书写顺序进行匹配。
简单记忆:
=(精确) > ^~(前缀) > ~(区分大小写正则) > ~*(不区分大小写正则) > /uri (前缀)> /(通用)
前缀匹配要最大;正则匹配要最先

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容