Nginx執行階段

Nginx 介紹
Nginx (engine x) 是一個高性能的HTTP和反向代理服務器,也是一個IMAP/POP3/SMTP服務器。
Nginx是一款輕量級的Web 服務器/反向代理服務器及電子郵件(IMAP/POP3)代理服務器,並在一個BSD-like 協議下發行。其特點是佔有內存少,併發能力強
OpenResty介紹
OpenResty 是一個基於 Nginx 與 Lua 的高性能 Web 平臺,其內部集成了大量精良的 Lua 庫、第三方模塊以及大多數的依賴項。用於方便地搭建能夠處理超高併發、擴展性極高的動態 Web 應用、Web 服務和動態網關

執行階段前言

location /test {
set $a 32;
echo $a;

set $a 56;
echo $a;
}

兩次都會輸出56,因爲set階段始終在content階段之前執行,跟代碼的先後順序無關。

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 認爲當前請求的來源地址是指定的某一個請求頭的值。下面這個例子就使用了 ngx_realip 模塊提供的 set_real_ip_from 和 real_ip_header

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";
    }
}

這裏的配置是讓 Nginx 把那些來自 127.0.0.1 的所有請求的來源地址,都改寫爲請求頭 X-My-IP 所指定的值。同時該例使用了標準內建變量 $remote_addr 來輸出當前請求的來源地址,以確認是否被成功改寫。

$ curl -H 'X-My-IP: 1.2.3.4' localhost:8080/test
    from: 1.2.3.4

server-rewrite階段

該階段包含標準函數ngx_rewrite、set 以及openresty函數set_by_lua、rewrite_by_lua
post-read 階段之後便是 server-rewrite 階段。當 ngx_rewrite 模塊的配置指令直接書寫在 server 配置塊中時,基本上都是運行在 server-rewrite 階段。

server {
    listen 8080;

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

    set $a hello;
}

這裏,配置語句 set $a hello 直接寫在了 server 配置塊中,因此它就運行在 server-rewrite 階段。而 server-rewrite 階段要早於 rewrite 階段運行,因此寫在 location 配置塊中的語句 set b"b "a, world" 便晚於外面的 set $a hello 語句運行。該例的測試結果證明了這一點:

$ curl localhost:8080/test
hello, world

find-config 階段

這個階段並不支持 Nginx 模塊註冊處理程序,而是由 Nginx 核心來完成當前請求與 location 配置塊之間的配對工作。

location /hello {
    echo "hello world";
}

rewrite 階段

該階段包含標準函數set_unescape_uri、rewrite以及openresty函數set_by_lua、 rewrite_by_lua

post-rewrite 階段

post-rewrite 階段,不接受 Nginx 模塊註冊處理程序,而是由 Nginx 核心完成 rewrite 階段所要求的“內部跳轉”操作
“內部跳轉”的工作原理:本質上其實就是把當前的請求處理階段強行倒退到 find-config 階段,以便重新進行請求 URI 與 location 配置塊的配對。比如例中,運行在 rewrite 階段的 rewrite 指令就讓當前請求的處理階段倒退回了 find-config 階段。由於此時當前請求的 URI 已經被 rewrite 指令修改爲了 /bar,所以這一次換成了 location /bar 與當前請求相關聯,然後再接着從 rewrite 階段往下執行。
爲什麼不直接在 rewrite 指令執行時立即進行跳轉呢?
爲了在最初匹配的 location 塊中支持多次反覆地改寫 URI

    server {
        listen 8080;

        location /foo {
            set $a hello;
            rewrite ^ /bar;
        }

        location /bar {
            echo "a = [$a]";
        }
    }
location /foo {
    rewrite ^ /bar;
    rewrite ^ /baz;

    echo foo;
}

location /bar {
    echo bar;
}

location /baz {
    echo baz;
}

注意的:如果在 server 配置塊中直接使用 rewrite 配置指令對請求 URI 進行改寫,則不會涉及“內部跳轉”

server {
listen 8080;

rewrite ^/foo /bar;

location /foo {
    echo foo;
}

location /bar {
    echo bar;
}
}

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 其實也在這個階段註冊了處理程序

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 相關聯。
建議是:儘量在 server 配置塊中配置 ngx_realip 這樣的模塊

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.

location /test {
    try_files /foo /bar/ /baz;
    echo "uri: $uri";
}

location /foo {
    echo foo;
}

location /bar/ {
    echo bar;
}

location /baz {
    echo baz;
}

我們在 location /test 中使用了 try_files 配置指令,並提供了三個參數,/foo、/bar/ 和 /baz. 根據前面對 try_files 指令的介紹,我們可以知道,它會在 try-files 階段依次檢查前兩個參數 /foo 和 /bar/ 所對應的文件系統對象是否存在。
不妨先來做一組實驗。假設現在 /var/www/ 路徑下是空的,則第一個參數 /foo 映射成的文件 /var/www/foo 是不存在的;同樣,對於第二個參數 /bar/ 所映射成的目錄 /var/www/bar/ 也是不存在的。於是此時 Nginx 會在 try-files 階段發起到最後一個參數所指定的 URI(即 /baz)的“內部跳轉”。實際的請求結果證實了這一點:

 $ curl localhost:8080/test
   baz

接下來再做一組實驗:在 /var/www/ 下創建一個名爲 foo 的文件,其內容爲 hello world(注意你需要有 /var/www/ 目錄下的寫權限):

$ echo 'hello world' > /var/www/foo

然後再請求 /test 接口:

 $ curl localhost:8080/test
  uri: /foo

這裏發生了什麼?我們來看, try_files 指令的第一個參數 /foo 可以映射爲文件 /var/www/foo,而 Nginx 在 try-files 階段發現此文件確實存在,於是立即把當前請求的 URI 改寫爲這個參數的值,即 /foo,並且不再繼續檢查後面的參數,而直接運行後面的請求處理階段。
通過前面這幾組實驗不難看到, try_files 指令本質上只是有條件地改寫當前請求的 URI,而這裏說的“條件”其實就是文件系統上的對象是否存在。當“條件”都不滿足時,它就會無條件地發起一個指定的“內部跳轉”。當然,除了無條件地發起“內部跳轉”之外, try_files 指令還支持直接返回指定狀態碼的 HTTP 錯誤頁,例如:

 try_files /foo /bar/ =404;

這行配置是說,當 /foo 和 /bar/ 參數所對應的文件系統對象都不存在時,就直接返回 404 Not Found 錯誤頁。注意這裏它是如何使用等號字符前綴來標識 HTTP 狀態碼的。

content階段

該階段包含標準函數echo proxy_pass 以及openresty 函數content_by_lua balance_by_lua header_filter_by_lua body_filter_by_lua
log

所有請求的標準輸出都在改階段。幾乎所有的邏輯代碼也在改階段執行。這個階段比較常見

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 方式。

location /test {
    satisfy all;

    deny all;
    access_by_lua 'ngx.exit(ngx.OK)';

    echo something important;
}

如果我們把上例中的 satisfy all 語句更改爲 satisfy any,

location /test {
    satisfy any;

    deny all;
    access_by_lua 'ngx.exit(ngx.OK)';

    echo something important;
}

結果則會完全不同:

$ curl localhost:8080/test
something important

在 any 方式下,access 階段只要有一個模塊通過了驗證,就會認爲請求整體通過了驗證,而在上例中, ngx_lua 模塊的 access_by_lua 語句總是會通過驗證的。

ngx_index 模塊, ngx_autoindex 模塊,以及 ngx_static 模塊

Nginx 一般會在 content 階段安排三個這樣的靜態資源服務模塊。按照它們在 content 階段的運行順序,依次是 ngx_index 模塊, ngx_autoindex 模塊,以及 ngx_static 模塊。
ngx_index 和 ngx_autoindex 模塊都只會作用於那些 URI 以 / 結尾的請求,例如請求 GET /cats/,而對於不以 / 結尾的請求則會直接忽略,同時把處理權移交給 content 階段的下一個模塊。而 ngx_static 模塊則剛好相反,直接忽略那些 URI 以 / 結尾的請求。
ngx_index 模塊主要用於在文件系統目錄中自動查找指定的首頁文件,類似 index.html 和 index.htm 這樣的,例如:

location / {
    root /var/www/;
    index index.htm index.html;
}

爲了進一步確認 ngx_index 模塊在找到文件時的“內部跳轉”行爲,我們不妨設計下面這個小例子:

location / {
    root /var/www/;
    index index.html;
}

location /index.html {
    set $a 32;
    echo "a = $a";
}

此時我們在本機的 /var/www/ 目錄下創建一個空白的 index.html 文件,並確保該文件的權限設置對於運行 Nginx worker 進程的帳戶可讀

$ curl 'http://localhost:8080/'
a = 32

如果此時把 /var/www/index.html 文件刪除,再訪問 / 又會發生什麼事情呢?答案是返回 403 Forbidden 出錯頁。爲什麼呢?因爲 ngx_index 模塊找不到 index 指令指定的文件(在這裏就是 index.html),接着把處理權轉給 content 階段的後續模塊,而後續的模塊也都無法處理這個請求,於是 Nginx 只好放棄,輸出了錯誤頁
運行在 ngx_index 模塊之後的 ngx_autoindex 模塊就可以用於自動生成這樣的“目錄索引”網頁。我們來把上例修改一下:

location / {
    root /var/www/;
    index index.html;
    autoindex on;
}

此時仍然保持文件系統中的 /var/www/index.html 文件不存在。我們再訪問 / 位置時,就會得到一張漂亮的網頁:

$ curl 'http://localhost:8080/'

ngx_static 模塊服務磁盤文件的例子。我們使用下面這個配置片段:
location / {
root /var/www/;
}

現在來通過 HTTP 協議請求一下這兩個文件所對應的 URI:

$ curl 'http://localhost:8080/index.html'
this is my home

$ curl 'http://localhost:8080/hello.html'
hello world

location / 中沒有使用運行在 content 階段的模塊指令,於是也就沒有模塊註冊這個 location 的“內容處理程序”,處理權便自動落到了在 content 階段“墊底”的那 3 個靜態資源服務模塊。首先運行的 ngx_index 和 ngx_autoindex 模塊先後看到當前請求的 URI,/index.html 和 /hello.html,並不以 / 結尾,於是直接棄權,將處理權轉給了最後運行的 ngx_static 模塊。ngx_static 模塊根據 root 指令指定的“文檔根目錄”(document root),分別將請求 URI /index.html 和 /hello.html 映射爲文件系統路徑 /var/www/index.html 和 /var/www/hello.html,在確認這兩個文件存在後,將它們的內容分別作爲響應體輸出,並自動設置 Content-Type、Content-Length 以及 Last-Modified 等響應頭。

很多初學者會想當然地把 404 錯誤理解爲某個 location 不存在,其實上面這個例子表明,即使 location 存在併成功匹配,也是可能返回 404 錯誤頁的。因爲決定着 404 錯誤頁的是抽象的“資源”是否存在,而非某個具體的 location 是否存在。
location /auth {
access_by_lua ’
';
}
顯然,這個 /auth 接口只定義了 access 階段的配置指令,即 access_by_lua,並未定義任何 content 階段的配置指令。於是當我們請求 /auth 接口時,在 access 階段的 Lua 代碼會如期執行,然後 content 階段的那些靜態文件服務會緊接着自動發生作用,直至 ngx_static 模塊去文件系統上找名爲 auth 的文件。而經常地,404 錯誤頁會拋出,除非運氣太好,在對應路徑上確實存在一個叫做 auth 的文件。所以,一條經驗是,當遇到意外的 404 錯誤並且又不涉及靜態文件服務時,應當首先檢查是否在對應的 location 配置塊中恰當地配置了 content 階段的模塊指令,例如 content_by_lua、 echo 以及 proxy_pass 之類。

openresty請求處理順序

set_by_lua*: 流程分支處理判斷變量初始化
rewrite_by_lua*: 轉發、重定向、緩存等功能(例如特定請求代理到外網)
access_by_lua*: IP 准入、接口權限等情況集中處理(例如配合 iptable 完成簡單防火牆)
content_by_lua*: 內容生成
header_filter_by_lua*: 響應頭部過濾處理(例如添加頭部信息)
body_filter_by_lua*: 響應體過濾處理(例如完成應答內容統一成大寫) log_by_lua*:會話完成後本地異步完成日誌記錄(日誌可以記錄在本地,還可以同步到其他機器)

在這裏插入圖片描述
內容全部來自於
http://blog.sina.com.cn/s/articlelist_1834459124_0_1.html
https://openresty.org/download/agentzh-nginx-tutorials-zhcn.html#02-NginxDirectiveExecOrder01

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章