Nginx服務(wù)器中l(wèi)ocation配置實(shí)例分析

??????? 首先我來(lái)大概的介紹一下location的種類和匹配規(guī)則,以nginx wiki的例子做說(shuō)明:

location?=?/?{? ?#?matches?the?query?/?only.? ?[?configuration?a?]?? }? location?/?{? ?#?matches?any?query,?since?all?queries?begin?with?/,?but?regular? ?#?expressions?and?any?longer?conventional?blocks?will?be? ?#?matched?first.? ?[?configuration?b?]?? }? location?^~?/images/?{? ?#?matches?any?query?beginning?with?/images/?and?halts?searching,? ?#?so?regular?expressions?will?not?be?checked.? ?[?configuration?c?]?? }? location?~*?.(gif|jpg|jpeg)$?{? ?#?matches?any?request?ending?in?gif,?jpg,?or?jpeg.?however,?all? ?#?requests?to?the?/images/?directory?will?be?handled?by? ?#?configuration?c.??? ?[?configuration?d?]?? }? ? location?@named?{? ?#?such?locations?are?not?used?during?normal?processing?of?requests,?? ?#?they?are?intended?only?to?process?internally?redirected?requests?(for?example?error_page,?try_files).? ?[?configuration?e?]?? }

??????? 可以看到上面的例子中有5種不同類型的location,其中第4個(gè)帶 “~” 號(hào)前綴的為需要正則匹配的location,nginx在進(jìn)行url解析時(shí)對(duì)這5種不同類型的location具有不同的優(yōu)先級(jí)規(guī)則,大致的規(guī)則如下:

1,字符串精確匹配到一個(gè)帶 “=” 號(hào)前綴的location,則停止,且使用這個(gè)location的配置;

2,字符串匹配剩下的非正則和非特殊location,如果匹配到某個(gè)帶 “^~” 前綴的location,則停止;

3,正則匹配,匹配順序?yàn)閘ocation在配置文件中出現(xiàn)的順序。如果匹配到某個(gè)正則location,則停止,并使用這個(gè)location的配置;否則,使用步驟2中得到的具有最大字符串匹配的location配置。

?????? 例如,對(duì)下面的請(qǐng)求有:

1, /?? ->?? 精確匹配到第1個(gè)location,匹配停止,使用configuration a
2,/some/other/url??? ->? 首先前綴部分字符串匹配到了第2個(gè)location,然后進(jìn)行正則匹配,顯然沒(méi)有匹配上,則使用第2個(gè)location的配置configurationb
3,/images /1.jpg? ->? 首先前綴部分字符串匹配到了第2個(gè)location,但是接著對(duì)第3個(gè)location也前綴匹配上了,而且這時(shí)已經(jīng)是配置文件里面對(duì)這個(gè)url的最大字符串匹配了,并且location帶有 “^~” 前綴,則不再進(jìn)行正則匹配,最終使用configuration c
4,/some/other/path/to/1.jpg? -> 首先前綴部分同樣字符串匹配到了第2個(gè)location,然后進(jìn)行正則匹配,這時(shí)正則匹配成功,則使用congifuration d

????? nginx的url匹配規(guī)則實(shí)際上有點(diǎn)不妥,大部分情況下一個(gè)url必須先進(jìn)行字符串匹配,然后再做正則匹配,但是實(shí)際上如果先做正則匹配,沒(méi)有匹配上再 做字符串匹配,在很多情況下可以節(jié)省掉做字符串匹配的時(shí)間。不管怎樣,先來(lái)看一下nginx源碼里面的實(shí)現(xiàn),在介紹匹配location過(guò)程之前,先來(lái)介 紹一下nginx里面對(duì)location的組織方式,實(shí)際上在配置解析階段,nginx將字符串匹配的location和正則匹配的location分別 存儲(chǔ)在http core模塊的loc配置ngx_http_core_loc_conf_t結(jié)構(gòu)的下面2個(gè)字段:

ngx_http_location_tree_node_t??*static_locations;? (ngx_pcre)? ngx_http_core_loc_conf_t????**regex_locations;? if

從這2個(gè)字段的類型可以看出,字符串匹配的location被組織成了一個(gè)location tree,而正則匹配的location只是一個(gè)數(shù)組,

location?tree和regex_locations數(shù)組建立過(guò)程在ngx_http_block中: /*?create?location?trees?*/? ? ??for?(s?=?0;?s?servers.nelts;?s++)?{? ? ????clcf?=?cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];? ? ????if?(ngx_http_init_locations(cf,?cscfp[s],?clcf)?!=?ngx_ok)?{? ??????return?ngx_conf_error;? ????}? ? ????if?(ngx_http_init_static_location_trees(cf,?clcf)?!=?ngx_ok)?{? ??????return?ngx_conf_error;? ????}? ??}

??????? 經(jīng)過(guò)配置的讀取之后,所有server都被保存在http core模塊的main配置中的servers數(shù)組中,而每個(gè)server里面的location都被按配置中出現(xiàn)的順序保存在http core模塊的loc配置的locations隊(duì)列中,上面的代碼中先對(duì)每個(gè)server的location進(jìn)行排序和分類處理,這一步發(fā)生在 ngx_http_init_location()函數(shù)中:

static?ngx_int_t? ngx_http_init_locations(ngx_conf_t?*cf,?ngx_http_core_srv_conf_t?*cscf,? ??ngx_http_core_loc_conf_t?*pclcf)? {? ?...? ??locations?=?pclcf->locations;? ? ?...? ??/*?按照類型排序location,排序完后的隊(duì)列:?(exact_match?或?inclusive)?(排序好的,如果某個(gè)exact_match名字和inclusive?location相同,exact_match排在前面)? ????|?regex(未排序)|?named(排序好的)?|?noname(未排序)*/? ??ngx_queue_sort(locations,?ngx_http_cmp_locations);? ? ??named?=?null;? ??n?=?0;? #if?(ngx_pcre)? ??regex?=?null;? ??r?=?0;? #endif? ? ??for?(q?=?ngx_queue_head(locations);? ?????q?!=?ngx_queue_sentinel(locations);? ?????q?=?ngx_queue_next(q))? ??{? ????lq?=?(ngx_http_location_queue_t?*)?q;? ? ????clcf?=?lq->exact???lq->exact?:?lq->inclusive;? ????/*?由于可能存在nested?location,也就是location里面嵌套的location,這里需要遞歸的處理一下當(dāng)前l(fā)ocation下面的nested?location?*/? ????if?(ngx_http_init_locations(cf,?null,?clcf)?!=?ngx_ok)?{? ??????return?ngx_error;? ????}? ? #if?(ngx_pcre)? ? ????if?(clcf->regex)?{? ??????r++;? ? ??????if?(regex?==?null)?{? ????????regex?=?q;? ??????}? ? ??????continue;? ????}? ? #endif? ? ????if?(clcf->named)?{? ??????n++;? ? ??????if?(named?==?null)?{? ????????named?=?q;? ??????}? ? ??????continue;? ????}? ? ????if?(clcf->noname)?{? ??????break;? ????}? ??}? ? ??if?(q?!=?ngx_queue_sentinel(locations))?{? ????ngx_queue_split(locations,?q,?&tail);? ??}? ??/*?如果有named?location,將它們保存在所屬server的named_locations數(shù)組中?*/? ??if?(named)?{? ????clcfp?=?ngx_palloc(cf->pool,? ??????????????(n?+?1)?*?sizeof(ngx_http_core_loc_conf_t?**));? ????if?(clcfp?==?null)?{? ??????return?ngx_error;? ????}? ? ????cscf->named_locations?=?clcfp;? ? ????for?(q?=?named;? ???????q?!=?ngx_queue_sentinel(locations);? ???????q?=?ngx_queue_next(q))? ????{? ??????lq?=?(ngx_http_location_queue_t?*)?q;? ? ??????*(clcfp++)?=?lq->exact;? ????}? ? ????*clcfp?=?null;? ? ????ngx_queue_split(locations,?named,?&tail);? ??}? ? #if?(ngx_pcre)? ??/*?如果有正則匹配location,將它們保存在所屬server的http?core模塊的loc配置的regex_locations?數(shù)組中,? ????這里和named?location保存位置不同的原因是由于named?location只能存在server里面,而regex?location可以作為nested?location?*/? ??if?(regex)?{? ? ????clcfp?=?ngx_palloc(cf->pool,? ??????????????(r?+?1)?*?sizeof(ngx_http_core_loc_conf_t?**));? ????if?(clcfp?==?null)?{? ??????return?ngx_error;? ????}? ? ????pclcf->regex_locations?=?clcfp;? ? ????for?(q?=?regex;? ???????q?!=?ngx_queue_sentinel(locations);? ???????q?=?ngx_queue_next(q))? ????{? ??????lq?=?(ngx_http_location_queue_t?*)?q;? ? ??????*(clcfp++)?=?lq->exact;? ????}? ? ????*clcfp?=?null;? ? ????ngx_queue_split(locations,?regex,?&tail);? ??}? ? #endif? ? ??return?ngx_ok;? }

??????????
?????? 上面的步驟將正則匹配的location保存好了,location tree的建立在ngx_http_init_static_location_trees中進(jìn)行:

static?ngx_int_t? ngx_http_init_static_location_trees(ngx_conf_t?*cf,? ??ngx_http_core_loc_conf_t?*pclcf)? {? ??ngx_queue_t????????*q,?*locations;? ??ngx_http_core_loc_conf_t??*clcf;? ??ngx_http_location_queue_t?*lq;? ? ??locations?=?pclcf->locations;? ? ??if?(locations?==?null)?{? ????return?ngx_ok;? ??}? ? ??if?(ngx_queue_empty(locations))?{? ????return?ngx_ok;? ??}? ??/*?這里也是由于nested?location,需要遞歸一下?*/? ??for?(q?=?ngx_queue_head(locations);? ?????q?!=?ngx_queue_sentinel(locations);? ?????q?=?ngx_queue_next(q))? ??{? ????lq?=?(ngx_http_location_queue_t?*)?q;? ? ????clcf?=?lq->exact???lq->exact?:?lq->inclusive;? ? ????if?(ngx_http_init_static_location_trees(cf,?clcf)?!=?ngx_ok)?{? ??????return?ngx_error;? ????}? ??}? ??/*?join隊(duì)列中名字相同的inclusive和exact類型location,也就是如果某個(gè)exact_match的location名字和普通字符串匹配的location名字相同的話,? ????就將它們合到一個(gè)節(jié)點(diǎn)中,分別保存在節(jié)點(diǎn)的exact和inclusive下,這一步的目的實(shí)際是去重,為后面的建立排序樹(shù)做準(zhǔn)備?*/? ??if?(ngx_http_join_exact_locations(cf,?locations)?!=?ngx_ok)?{? ????return?ngx_error;? ??}? ??/*?遞歸每個(gè)location節(jié)點(diǎn),得到當(dāng)前節(jié)點(diǎn)的名字為其前綴的location的列表,保存在當(dāng)前節(jié)點(diǎn)的list字段下?*/? ??ngx_http_create_locations_list(locations,?ngx_queue_head(locations));? ? ??/*?遞歸建立location三叉排序樹(shù)?*/? ??pclcf->static_locations?=?ngx_http_create_locations_tree(cf,?locations,?0);? ??if?(pclcf->static_locations?==?null)?{? ????return?ngx_error;? ??}? ? ??return?ngx_ok;? }

??????? 經(jīng)過(guò)ngx_http_init_location()函數(shù)處理之后,locations隊(duì)列已經(jīng)是排好序的了,建立三叉樹(shù)的過(guò)程的主要工作都在ngx_http_create_locations_list()和ngx_http_create_locations_tree()中完成,這2個(gè) 函數(shù)都是遞歸函數(shù),第1個(gè)函數(shù)遞歸locations隊(duì)列中的每個(gè)節(jié)點(diǎn),得到以當(dāng)前節(jié)點(diǎn)的名字為前綴的location,并保存在當(dāng)前節(jié)點(diǎn)的list字段 下,例如,對(duì)下列l(wèi)ocation:

location?/xyz?{? ? }? ? location?=?/xyz?{? ? }? location?/xyza?{? ? }? ? location?/xyzab?{? ? }? location?/xyzb?{? ? }? location?/abc?{? ? }? location?/efg?{? ? }? location?/efgaa?{? ? }

??????? 排序的結(jié)果為/abc? /efg?? /efgaa? =/xyz? /xyz? /xyza /xyzab /xyzb,去重后結(jié)果為 /abc? /efg?? /efgaa?? /xyz? /xyza /xyzab/xyzb,ngx_http_create_locations_list()執(zhí)行后的結(jié)果為:

Nginx服務(wù)器中l(wèi)ocation配置實(shí)例分析

?最后,來(lái)看下ngx_http_create_locations_tree函數(shù):

static?ngx_http_location_tree_node_t?*? ngx_http_create_locations_tree(ngx_conf_t?*cf,?ngx_queue_t?*locations,? ??size_t?prefix)? {? ??...? ??/*?根節(jié)點(diǎn)為locations隊(duì)列的中間節(jié)點(diǎn)?*/? ??q?=?ngx_queue_middle(locations);? ? ??lq?=?(ngx_http_location_queue_t?*)?q;? ??len?=?lq->name->len?-?prefix;? ??? ??node?=?ngx_palloc(cf->pool,? ???????????offsetof(ngx_http_location_tree_node_t,?name)?+?len);? ??if?(node?==?null)?{? ????return?null;? ??}? ? ??node->left?=?null;? ??node->right?=?null;? ??node->tree?=?null;? ??node->exact?=?lq->exact;? ??node->inclusive?=?lq->inclusive;? ? ??node->auto_redirect?=?(u_char)?((lq->exact?&&?lq->exact->auto_redirect)? ??????????????||?(lq->inclusive?&&?lq->inclusive->auto_redirect));? ? ??node->len?=?(u_char)?len;? ??ngx_memcpy(node->name,?&lq->name->data[prefix],?len);? ? ??/*?從中間節(jié)點(diǎn)開(kāi)始斷開(kāi)?*/? ??ngx_queue_split(locations,?q,?&tail);? ? ??if?(ngx_queue_empty(locations))?{? ????/*? ?????*?ngx_queue_split()?insures?that?if?left?part?is?empty,? ?????*?then?right?one?is?empty?too? ?????*/? ????goto?inclusive;? ??}? ? ??/*?從locations左半部分得到左子樹(shù)?*/? ??node->left?=?ngx_http_create_locations_tree(cf,?locations,?prefix);? ??if?(node->left?==?null)?{? ????return?null;? ??}? ? ??ngx_queue_remove(q);? ? ??if?(ngx_queue_empty(&tail))?{? ????goto?inclusive;? ??}? ?? ? ??/*?從locations右半部分得到右子樹(shù)?*/? ??node->right?=?ngx_http_create_locations_tree(cf,?&tail,?prefix);? ??if?(node->right?==?null)?{? ????return?null;? ??}? ? inclusive:? ? ??if?(ngx_queue_empty(&lq->list))?{? ????return?node;? ??}? ? ??/*?從list隊(duì)列得到tree子樹(shù)?*/? ??node->tree?=?ngx_http_create_locations_tree(cf,?&lq->list,?prefix?+?len);? ??if?(node->tree?==?null)?{? ????return?null;? ??}? ? ??return?node;? }? ?????location?tree節(jié)點(diǎn)的ngx_http_location_tree_node_s結(jié)構(gòu): struct?ngx_http_location_tree_node_s?{? ??ngx_http_location_tree_node_t??*left;? ??ngx_http_location_tree_node_t??*right;? ??ngx_http_location_tree_node_t??*tree;? ? ??ngx_http_core_loc_conf_t????*exact;? ??ngx_http_core_loc_conf_t????*inclusive;? ? ??u_char??????????????auto_redirect;? ??u_char??????????????len;? ??u_char??????????????name[1];? };

???????? location tree結(jié)構(gòu)用到的是left,right,tree 這3個(gè)字段, location tree實(shí)際上是一個(gè)三叉的字符串排序樹(shù),而且這里如果某個(gè)節(jié)點(diǎn)只考慮左,右子樹(shù),它是一顆平衡樹(shù),它的建立過(guò)程有點(diǎn)類似于一顆平衡排序二叉樹(shù)的建立過(guò)程,先排序再用二分查找找到的節(jié)點(diǎn)順序插入,ngx_http_location_tree_node_s的tree節(jié)點(diǎn)也是一顆平衡排序樹(shù),它是用該節(jié)點(diǎn)由ngx_http_create_locations_list()得到的list建立的,也就是該節(jié)點(diǎn)的名字是它的tree子樹(shù)里面的所有節(jié)點(diǎn)名字的前綴,所以tree子樹(shù)里面的所有節(jié)點(diǎn)的名字不用保存公共前綴,而且查找的時(shí)候,如果是轉(zhuǎn)向tree節(jié)點(diǎn)的話,也是不需要再比較父節(jié)點(diǎn)的那段字符串了。
???????? ngx_http_create_locations_tree()函數(shù)寫(xiě)的很清晰,它有一個(gè)參數(shù)是隊(duì)列l(wèi)ocations,它返回一顆三叉樹(shù),根節(jié)點(diǎn)為locations的中間節(jié)點(diǎn),其左子樹(shù)為locations隊(duì)列的左半部分建立的location tree,右子樹(shù)為location隊(duì)列的右半部分建立的tree,tree節(jié)點(diǎn)為該根節(jié)點(diǎn)的list隊(duì)列建立的tree。

?????? 最終建立的location tree如下(為了方便閱讀,圖中列出了tree節(jié)點(diǎn)的完整名字):

Nginx服務(wù)器中l(wèi)ocation配置實(shí)例分析

ps:關(guān)于 location modifier
1. =
這會(huì)完全匹配指定的 pattern ,且這里的 pattern 被限制成簡(jiǎn)單的字符串,也就是說(shuō)這里不能使用正則表達(dá)式。

example: server?{ ??server_name?jb51.net; ??location?=?/abcd?{ ??[…] ??} }

匹配情況:

??http://jb51.net/abcd????#?正好完全匹配 ??http://jb51.net/abcd????#?如果運(yùn)行?nginx?server?的系統(tǒng)本身對(duì)大小寫(xiě)不敏感,比如?windows?,那么也匹配 ??http://jb51.net/abcd?param1?m2??#?忽略查詢串參數(shù)(query?string?arguments),這里就是?/abcd?后面的??param1?m2 ??http://jb51.net/abcd/??#?不匹配,因?yàn)槟┪泊嬖诜葱备埽╰railing?slash),nginx?不認(rèn)為這種情況是完全匹配 ??http://jb51.net/abcde??#?不匹配,因?yàn)椴皇峭耆ヅ?/pre>

2. (none)
可以不寫(xiě) location modifier ,nginx 仍然能去匹配 pattern 。這種情況下,匹配那些以指定的 patern 開(kāi)頭的 uri,注意這里的 uri 只能是普通字符串,不能使用正則表達(dá)式。

example: server?{ ??server_name?jb51.net; ??location?/abcd?{ ??[…] ??} }

匹配情況:

??http://jb51.net/abcd????#?正好完全匹配 ??http://jb51.net/abcd????#?如果運(yùn)行?nginx?server?的系統(tǒng)本身對(duì)大小寫(xiě)不敏感,比如?windows?,那么也匹配 ??http://jb51.net/abcd?param1?m2??#?忽略查詢串參數(shù)(query?string?arguments),這里就是?/abcd?后面的??param1?m2 ??http://jb51.net/abcd/??#?末尾存在反斜杠(trailing?slash)也屬于匹配范圍內(nèi) ??http://jb51.net/abcde??#?仍然匹配,因?yàn)?uri?是以?pattern?開(kāi)頭的

3. ~
這個(gè) location modifier 對(duì)大小寫(xiě)敏感,且 pattern 須是正則表達(dá)式

example: server?{ ??server_name?jb51.net; ??location?~?^/abcd$?{ ??[…] ??} }

匹配情況:

??http://jb51.net/abcd????#?完全匹配 ??http://jb51.net/abcd????#?不匹配,~?對(duì)大小寫(xiě)是敏感的 ??http://jb51.net/abcd?param1?m2??#?忽略查詢串參數(shù)(query?string?arguments),這里就是?/abcd?后面的??param1?m2 ??http://jb51.net/abcd/??#?不匹配,因?yàn)槟┪泊嬖诜葱备埽╰railing?slash),并不匹配正則表達(dá)式?^/abcd$ ??http://jb51.net/abcde??#?不匹配正則表達(dá)式?^/abcd$

注意:對(duì)于一些對(duì)大小寫(xiě)不敏感的系統(tǒng),比如 windows ,~ 和 ~* 都是不起作用的,這主要是操作系統(tǒng)的原因。

4. ~*
與 ~ 類似,但這個(gè) location modifier 不區(qū)分大小寫(xiě),pattern 須是正則表達(dá)式

example: server?{ ??server_name?jb51.net; ??location?~*?^/abcd$?{ ??[…] ??} }

匹配情況:
??

?http://jb51.net/abcd????#?完全匹配 ??http://jb51.net/abcd????#?匹配,這就是它不區(qū)分大小寫(xiě)的特性 ??http://jb51.net/abcd?param1?m2??#?忽略查詢串參數(shù)(query?string?arguments),這里就是?/abcd?后面的??param1?m2 ??http://jb51.net/abcd/??#?不匹配,因?yàn)槟┪泊嬖诜葱备埽╰railing?slash),并不匹配正則表達(dá)式?^/abcd$ ??http://jb51.net/abcde??#?不匹配正則表達(dá)式?^/abcd$

5. ^~
匹配情況類似 2. (none) 的情況,以指定匹配模式開(kāi)頭的 uri 被匹配,不同的是,一旦匹配成功,那么 nginx 就停止去尋找其他的 location 塊進(jìn)行匹配了(與 location 匹配順序有關(guān))

6. @
用于定義一個(gè) location 塊,且該塊不能被外部 client 所訪問(wèn),只能被 nginx 內(nèi)部配置指令所訪問(wèn),比如 try_files or error_page

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊10 分享