Nginx指令阶段和常见错误总结

Nginx指令阶段

因为nginx执行是按阶段执行的,我们一般的程序都是按顺序执行的。nginx指令一般是分布在不同阶段的;
Nginx 处理请求的过程一共划分为 11 个阶段,按照执行顺序依次是 post-read、server-rewrite、find-config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content 以及 log.

  • post-read阶段
    该阶段Nginx标准函数 set_real_ip_from、real_ip_header
    最先执行的 post-read 阶段在 Nginx 读取并解析完请求头(request headers)之后就立即开始运行。标准模块 ngx_realip 就在 post-read 阶段注册了处理程序,它的功能是迫使 Nginx 认为当前请求的来源地址是指定的某一个请求头的值。
// eg:
server {
    listen 8080;
    set_real_ip_from 127.0.0.1;
    real_ip_header   X-My-IP;

    location /test {
        set $addr $remote_addr;
        echo "from: $addr";
    }
}
  • server-rewrite阶段
    该阶段包含标准函数ngx_rewrite、set 以及openresty函数set_by_lua、rewrite_by_lua
    post-read 阶段之后便是 server-rewrite 阶段。当 ngx_rewrite 模块的配置指令直接书写在 server 配置块中时,基本上都是运行在 server-rewrite 阶段。
// eg:
server {
    listen 8080;

    location /test {
        set $b "$a, world";
        echo $b;
    }

    set $a hello;
}
  • find-config 阶段
    这个阶段并不支持 Nginx 模块注册处理程序,而是由 Nginx 核心来完成当前请求与 location 配置块之间的配对工作。
location /hello {
    echo "hello world";
}
  • rewrite 阶段
    该阶段包含标准函数set_unescape_uri、rewrite以及openresty函数set_by_lua、 rewrite_by_lua

  • post-rewrite 阶段,
    不接受 Nginx 模块注册处理程序,而是由 Nginx 核心完成 rewrite 阶段所要求的“内部跳转”操作
    “内部跳转”的工作原理:本质上其实就是把当前的请求处理阶段强行倒退到 find-config 阶段,以便重新进行请求 URI 与 location 配置块的配对。比如例中,运行在 rewrite 阶段的 rewrite 指令就让当前请求的处理阶段倒退回了 find-config 阶段。由于此时当前请求的 URI 已经被 rewrite 指令修改为了 /bar,所以这一次换成了 location /bar 与当前请求相关联,然后再接着从 rewrite 阶段往下执行。
    注意的:如果在 server 配置块中直接使用 rewrite 配置指令对请求 URI 进行改写,则不会涉及“内部跳转”

  • preaccess 阶段
    该阶段包含标准函数ngx_access-allow deny ngx_limit_req 和 ngx_limit_zone ngx_auth_request 以及openresty函数access_by_lua其中也包含了限频限流模块resty.limit.req resty.limit.conn
    注意的是:标准模块 ngx_realip 其实也在这个阶段注册了处理程序

// eg
server {
    listen 8080;

    location /test {
        set_real_ip_from 127.0.0.1;
        real_ip_header X-Real-IP;

        echo "from: $remote_addr";
    }
}

与先看前到的例子相比,此例最重要的区别在于把 ngx_realip 的配置指令放在了 location 配置块中。前面我们介绍过,Nginx 匹配 location 的动作发生在 find-config 阶段,而 find-config 阶段远远晚于 post-read 阶段执行,所以在 post-read 阶段,当前请求还没有和任何 location 相关联。

  • post-access阶段
    该阶段不支持 Nginx 模块注册处理程序,而是由 Nginx 核心自己完成一些处理工作
  • try-files 阶段
    实现标准配置指令 try_files 的功能,并不支持 Nginx 模块注册处理程序。
    try_files 指令接受两个以上任意数量的参数,每个参数都指定了一个 URI. 这里假设配置了 N 个参数,则 Nginx 会在 try-files 阶段,依次把前 N-1 个参数映射为文件系统上的对象(文件或者目录),然后检查这些对象是否存在。一旦 Nginx 发现某个文件系统对象存在,就会在 try-files 阶段把当前请求的 URI 改写为该对象所对应的参数 URI(但不会包含末尾的斜杠字符,也不会发生 “内部跳转”)。如果前 N-1 个参数所对应的文件系统对象都不存在,try-files 阶段就会立即发起“内部跳转”到最后一个参数(即第 N 个参数)所指定的 URI.
// eg1
location /test {
    try_files /foo /bar/ /baz;
    echo "uri: $uri";
}
// eg2
location /test {
    try_files /foo /bar/ =404;
    echo "uri: $uri";
}
  • content阶段
    该阶段包含标准函数echo proxy_pass 以及openresty 函数content_by_lua balance_by_lua header_filter_by_lua body_filter_by_lua
    所有请求的标准输出都在该阶段。几乎所有的逻辑代码也在该阶段执行。这个阶段比较常见

  • log阶段
    改阶段包含ngx的acces_log error_log以及openresty函数log_by_lua
    该阶段主要记录日志

  • satisfy指令
    对于多个 Nginx 模块注册在 access 阶段的处理程序, satisfy 配置指令可以用于控制它们彼此之间的协作方式。比如模块 A 和 B 都在 access 阶段注册了与访问控制相关的处理程序,那就有两种协作方式,一是模块 A 和模块 B 都得通过验证才算通过,二是模块 A 和模块 B 只要其中任一个通过验证就算通过。第一种协作方式称为 all 方式(或者说“与关系”),第二种方式则被称为 any 方式(或者说“或关系”)。默认情况下,Nginx 使用的是 all 方式。

// eg1
location /test {
    satisfy all;
    deny all;
    access_by_lua 'ngx.exit(ngx.OK)';
    echo something important;
}
// eg2
location /test {
    satisfy any;
    deny all;
    access_by_lua 'ngx.exit(ngx.OK)';
    echo something important;
}
nginx中邪恶的if指令

if指令用来判断条件为true时要执行的指令,条件false时不执行相应的指令,if指令只能用在server、location内。
因为nginx执行是按阶段执行的,我们一般的程序都是按顺序执行的。nginx指令一般是分布在不同阶段的;

  • 用 if 判断 Server Name
// 不推荐配置
server {
    server_name example.com *.example.com;
        if ($host ~* ^www\.(.+)) {
            set $raw_domain $1;
            rewrite ^/(.*)$ $raw_domain/$1 permanent;
        }
        # [...]
    }
}
// 推荐
server {
    server_name www.example.com;
    return 301 $scheme://example.com$request_uri;
}
server {
    server_name example.com;
}

  • 用 if 检查文件是否存在
// 不推荐配置
server {
    root /var/www/example.com;
    location / {
        if (!-f $request_filename) {
            break;
        }
    }
}
// 推荐配置
server {
    root /var/www/example.com;
    location / {
        try_files $uri $uri/ /index.html;
    }
}
费力的 rewrites

rewrite 很容易和正则表达式混为一谈。 实际上,rewrite 是很容易的,我们应该努力去保持它们的整洁。

// 不推荐
rewrite ^/(.*)$ http://example.com/$1 permanent;
// 好一些
rewrite ^ http://example.com$request_uri? permanent;
// 更好些
return 301 http://example.com$request_uri;

忽略 http:// 的 rewrite

// 不推荐
rewrite ^ example.com permanent;
// 推荐
rewrite ^ http://example.com permanent;

没有使用标准的 Document Root Location

server {
    root /;
    location / {
        try_files /web/$uri $uri @php;
    }
    location @php {
    }
}
nginx配置的常见陷阱
  • 把 root 放在 location 区块内
    错误案例
server {
        server_name www.example.com;
        location / {
            root /var/www/Nginx -default/;
            # [...]
          }
        location /foo {
            root /var/www/Nginx -default/;
            # [...]
        }
  }

正确案例

server {
        server_name www.example.com;
        root /var/www/Nginx -default/;
        location / {
            # [...]
        }
        location /foo {
            # [...]
        }
}
  • 重复index指令
    错误案例
http {
    index index.php index.htm index.html;
    server {
        server_name www.example.com;
        location / {
            index index.php index.htm index.html;
        }
    }
    server {
        server_name example.com;
        location / {
            index index.php index.htm index.html;
        }
        location /foo {
            index index.php;
        }
    }
}

正确案例

http {
    index index.php index.htm index.html;
    server {
        server_name www.example.com;
        location / {
        }
    }
    server {
        server_name example.com;
        location / {
        }
        location /foo {
        }
    }
}

你可能感兴趣的:(Nginx指令阶段和常见错误总结)