在編寫nginx配置中,正確的配置location指令是相當(dāng)重要的。筆者在一次問題的排查中仔細(xì)研究了下這個指令的規(guī)則和使用方法,在這里和大家分享一下,如有不正確的地方還望指正。
指令語法
location [ 限定符 ] 規(guī)則uri { 指令集 }
在一個請求的處理流程中,nginx會啟動一個location的查找過程,在這個過程中nginx會根據(jù)一定的規(guī)則用請求的request_uri有選擇的去匹配這些定義好的規(guī)則uri,之后會找到要應(yīng)用的唯一的location,并應(yīng)用這個location定義的指令集中的指令。
這里面最關(guān)鍵的地方,就是location的查找過程是如何進(jìn)行的。要明白這個過程,首先需要知道規(guī)則uri是如何定義的。
規(guī)則uri
規(guī)則uri分為三種類型,nginx對這幾種類型執(zhí)行的匹配方法是不同的:
1. 對限定符為=的規(guī)則執(zhí)行完全匹配
完全匹配例:location = index.php {},那么僅有請求:http://domain/index.php才能匹配,http://domain/index.phpp或http://domain/iindex.php這種均無法匹配。
2. 對限定符為^~或沒有限定符規(guī)則執(zhí)行前綴匹配。
前綴匹配例:location /abc {}或location ^~ /abc {},那么請求:http://domain/abcd、http://domain/abc.php這種均能匹配。
3. 對限定符為~或~*的規(guī)則執(zhí)行正則匹配。
正則匹配例:location ~ .php$ {},那么請求:http://domain/abc.php、http://domain/abcd.php這種均可以匹配。
location的查找過程
首先會對所有的等號規(guī)則和前綴規(guī)則進(jìn)行一次匹配篩選。nginx會在所有的前綴匹配中找到一個最長的匹配,然后記住這個匹配的location。之后會根據(jù)這個匹配規(guī)則是否有限定符來決定之后的行為:
如果是
等號規(guī)則匹配到的,nginx會立即結(jié)束查找過程,這個location就是最終結(jié)果。如果匹配到的規(guī)則被限定符
^~修飾,則nginx也會結(jié)束這個查找過程,這個location就是最終結(jié)果。如果沒有找到匹配或是匹配項(xiàng)沒有被限定符修飾,nginx就會進(jìn)入
正則規(guī)則的匹配篩選過程。在這個過程中,nginx會按照規(guī)則uri在配置中定義的順序來進(jìn)行匹配,只要匹配到其中一個,nginx就會立即結(jié)束這個匹配過程,這個location也會是最終location。如果沒有能找到一個正則匹配,但是之前的前綴匹配成功,那么之前記住的前綴匹配結(jié)果則會成為最終結(jié)果。
通過上面的查找過程,如果成功找到匹配的location,則會執(zhí)行執(zhí)行這個location定義的指令集中的指令。
實(shí)例說明
下面舉幾個例子來看一下:假設(shè)用戶均訪問http://www.vmc7.com/abc
1. =命中,跳過正則匹配
server {
listen 80;
server_name www.vmc7.com;
location /abc {}
location = /abc {}
location ~ /abc {}
}
2. ^~命中,跳過正則匹配
server {
listen 80;
server_name www.vmc7.com;
location /ab {}
location ^~ /abc {}
location ~ /abc {}
}
3. 正則命中1,正則匹配優(yōu)先于前綴匹配
server {
listen 80;
server_name www.vmc7.com;
location /abc {}
location ~ /ab.* {}
}
4. 正則命中2,正則按順序匹配
server {
listen 80;
server_name www.vmc7.com;
location /ab {}
location ~ /ab.* {}
location ~ /abc {}
}
5. 前綴命中,正則匹配不到,使用之前命中的前綴匹配
server {
listen 80;
server_name www.vmc7.com;
location /abc {}
location ~ /ef.* {}
}
最后附上偽代碼用以描述整個過程
location = findLocation();
if (location != null) {
runLocationCmdSet();
}
function findLocation()
{
finalLocation = null;
prefixLocation = null;
regexLocation = null;
prefixLocation = findByPrefix();
if (prefixLocation != null) {
if (preceding(prefixLocation) == '=') {
return prefixLocation;
}
if (preceding(prefixLocation) == '^~') {
return prefixLocation;
}
}
regexLocation = findByRegex();
finalLocation = (regexLocation == null) ? prefixLocation : regexLocation;
return finalLocation;
}
function findByPrefix()
{
result = null;
maxLength = 0;
for (location in allPrefixLocation) {
if ((matchLength = prefixMatch(location))!= 0) {
if (matchLength > maxLength) {
maxLength = matchLength;
result = location;
}
}
}
return result;
}
function findByRegex()
{
for (location in allRegexLocation) {
if (regexMatch(location)) {
return location;
}
}
return null;
}