Nginx的server_name和location配置

Nginx是目前最流行的Web服務(wù)器,由于具備高性能、高可靠以及支持熱部署等特性被人們所青睞。Nginx用途廣泛,其可作為靜態(tài)資源服務(wù)器,也可充當(dāng)代理服務(wù)器(HTTP/TCP/UDP/MAIL等),還可以用來實現(xiàn)一些簡單的API服務(wù)。Nginx主要是通過其配置文件(一般名為nginx.conf)來控制它的行為,本文主要介紹其http模塊下的 server_namelocation這兩條指令的配置。

server指令塊與虛擬主機(jī)

虛擬主機(jī)是一種在單一主機(jī)或主機(jī)群上運(yùn)行多個網(wǎng)站或服務(wù)的技術(shù),可以用來解決IP地址資源有限而網(wǎng)站數(shù)目日益增多的問題。實現(xiàn)方式主要有以下三種:

  • 基于域名(Name-based)
  • 基于IP地址(IP-based)
  • 基于Port端口(Port-based)

其中使用最廣泛無疑是基于域名的方式,不同的域名通過DNS最終可以解析到相同的IP地址,在對應(yīng)的機(jī)器上我們可以使用Nginx等Web服務(wù)器軟件對不同的域名請求進(jìn)行相應(yīng)的處理。這里再提及一點,我們平時訪問一個網(wǎng)站,是通過DNS將其解析到某一個IP上,我們的客戶端(通常是瀏覽器)最終是和這個IP對應(yīng)的機(jī)器建立連接,從而發(fā)送請求的。那么Nginx等服務(wù)器是如何知道一個請求對應(yīng)的是哪個域名的呢?

答案在于HTTP協(xié)議中的Host請求頭,其值為我們要訪問的域名。這里需要注意的是,在HTTP/1.0中是不支持Host請求頭字段的,所以HTTP/1.0是不支持虛擬主機(jī)技術(shù)的,而根據(jù)rfc2616規(guī)范HTTP/1.1協(xié)議中客戶端發(fā)送的請求必須帶上Host這個請求頭,否則服務(wù)器必須返回400 Bad Request響應(yīng)。

而nginx正是通過http模塊下的server指令塊來配置虛擬主機(jī)。

server_name指令

配置語法:

Syntax: server_name name ...;
Default:    
server_name "";
Context: server

server_name形式

sever_name指令后面的參數(shù)值可以是以下幾種:

  • 精確的域名,例如www.example.com
  • 通配符名稱,可用*表示任意多字符(類似Linux Shell中的*),但是通配符必須在域名的最前面或者最后面,例如*.example.comwww.example.*
  • 正則表達(dá)式,最前面是一個波浪號~,例如~^www\d+\.example\.com$表示可以匹配以www開頭,后跟一個到多個數(shù)字,然后以.example.com結(jié)尾的域名

除了以上幾種形式,還有下面幾種表示特殊含義的域名:

  • .example.com,相當(dāng)于*.example.com+example.com
  • "",可以匹配沒有帶Host頭的請求
  • 國際化域名(用得不多,了解即可),用ASCII碼表示,例如xn--e1afmkfd.xn--80akhbyknj4f可表示пример.испытание
  • ___或者!@#等無效的域名,可以理解為其可以匹配任意域名,但是優(yōu)先級最低,最常見的用法是用來設(shè)置默認(rèn)的server,即當(dāng)一個請求的Host沒有命中其他規(guī)則時,會采用默認(rèn)server的配置。配置如下:
server {
    listen       80  default_server;
    server_name  _;
    return       444;
}

server_name匹配順序

當(dāng)需要決定采用哪個server塊的配置處理請求時,會根據(jù)以下的順序查找:

  1. 精確匹配
  2. 以*開頭的最長通配符名稱
  3. 以*結(jié)尾的最長通配符名稱
  4. 根據(jù)在配置文件出現(xiàn)的順序第一個匹配上的正則表示式名稱
  5. 默認(rèn)配置,在listen指令中指明了default_server的server塊,若無,為配置文件中第一個聲明的server塊

示例,假設(shè)nginx只有以下server配置:

    # 這里主要是方便下面輸出結(jié)果可以直接在瀏覽器顯示
    default_type  text/plain;
    # 這里使用geo指令主要是為了輸出$,直接在return輸出$會報錯
    # 參見https://stackoverflow.com/questions/57466554/how-do-i-escape-in-nginx-variables
    geo $dollar {
        default "$";
    }

    server {
        listen 80;
        server_name ~^www\.a\..*$;
        return 200 "~^www\.a\..*$dollar";
    }

    server {
        listen 80;
        server_name ~^.*a\..*$;
        return 200 "~^.*a\..*$dollar";
    }

    server {
        listen 80;
        server_name www.code.a.*;
        return 200 "www.code.a.*";
    }

    server {
        listen 80;
        server_name *.a.com;
        return 200 "*.a.com";
    }

    server {
        listen       80;
        server_name  www.a.com;
        return 200 "www.a.com";
    }

在hosts文件上加上以下配置:

127.0.0.1 www.a.com www.code.a.com www.code.a.cn www.a.oa.com dev.a.cn www.b.com

我們可以直接用瀏覽器訪問或者借助curl工具來進(jìn)行測試,測試結(jié)果如下,可對照上面的查找順序進(jìn)行分析:

input output 匹配類型
http://www.a.com www.a.com 精確匹配
http://www.code.a.com *.a.com 前導(dǎo)*匹配
http://www.code.a.cn www.code.a.* 后導(dǎo)*匹配
http://www.a.oa.com ~^www\.a\..*$ 正則匹配
http://dev.a.cn ~^.*a\..*$ 正則匹配
http://www.b.com ~^www\.a\..*$ 默認(rèn)匹配

值得說明的是,由于上面的配置沒有顯示指定默認(rèn)server,所以會默認(rèn)匹配到第一個配置,假如我們在配置最后再添加如下配置:

    server {
        listen 80 default_server;
        server_name _;
        return 200 "default_server";
    }

重啟后,再訪問http://www.b.com,會輸出default_server,其他訪問結(jié)果不變。注意這里的default_server是配置在listen指令下的。

關(guān)于listen指令,有幾點需要注意的地方:

  1. 如果server指令塊里沒有指定listen指令,則根據(jù)運(yùn)行nginx的用戶不同,默認(rèn)監(jiān)聽的端口也不同,root用戶啟動默認(rèn)監(jiān)聽80端口,否則默認(rèn)監(jiān)聽8000端口
  2. 如果配置了listen且只指定了IP,則監(jiān)聽端口為80,此時操作系統(tǒng)可能會不允許非root用戶啟動nginx,提示
nginx: [emerg] bind() to 127.0.0.1:80 failed (13: Permission denied)
  1. 以上說的配置查找規(guī)則前提是請求需要跟listen指令配置的IP跟端口相匹配
    關(guān)于以上注意事項,這里舉兩個例子:
  • 假設(shè)運(yùn)行nginx的是非root用戶,且上面最后我們加的配置里listen指令沒有指定80端口,即:
    server {
        listen default_server;
        server_name _;
        return 200 "default_server";
    }

這時訪問http://www.b.com,由于上面這個server監(jiān)聽的是8000端口,跟請求的80端口不匹配,結(jié)果將會變回~^www\.a\..*

  • 假設(shè)最后的默認(rèn)server配置改成如下配置(注意端口前有IP):
    server {
        listen 公網(wǎng)IP:80 default_server;
        server_name _;
        return 200 "default_server";
    }

這時如果是在公網(wǎng)訪問的話,不管訪問上面的哪個域名都會返回"default_server",理由是不設(shè)置IP的話nginx默認(rèn)會監(jiān)聽該機(jī)器的所有IP的特定端口,設(shè)置了的話只會監(jiān)聽該IP的特定端口。
本地訪問同理,不能匹配到listen了公網(wǎng)IP的server。

location 配置

了解完server_name和listen的配置規(guī)則,我們知道了一個請求過來會對應(yīng)哪個server。接下來我們要討論的是某個server下不同請求URI對應(yīng)的location配置查找規(guī)則。

配置語法

Syntax: location [ = | ~ | ~* | ^~ ] uri { ... }
location @name { ... }
Default: —
Context: server, location

根據(jù)配置語法我們知道location可以有以下幾種形式:

  • =,精確匹配
  • ~,正則匹配,大小寫敏感
  • ~*,正則匹配, 大小寫不敏感
  • ^~,忽略正則表達(dá)式的前綴匹配
  • 沒有修飾符,前綴匹配
  • @,命名location,可用來做內(nèi)部重定向

其中=和^~修飾符都可以認(rèn)為是特殊形式的前綴匹配

匹配過程

根據(jù)請求的URI和location的配置,查找請求對應(yīng)的location過程如下:

  1. 將請求URI標(biāo)準(zhǔn)化,包括將"%xx"形式編碼的文本進(jìn)行解碼,解析相對路徑"."和"..",以及合并兩個或多個相鄰的"/"成單個"/"
  2. 根據(jù)請求URI找到并記錄匹配上的最長前綴匹配,這里有兩個特殊的場景:
  • 找到了=修飾的精確匹配,結(jié)束查找,采用它的配置
  • 如果該步驟最終記錄下的前綴以^~修飾,則采用它的配置,不會進(jìn)行后續(xù)的查找步驟
  1. 根據(jù)在配置文件出現(xiàn)的順序,檢查相應(yīng)的正則匹配,若有一個匹配上,則應(yīng)用該配置,且不會繼續(xù)檢查后續(xù)的正則配置
  2. 若第3步?jīng)]有找到匹配上的正則匹配,則采用第2步中找到的最長前綴匹配對應(yīng)的配置

根據(jù)上面的查找過程,可以得到一些配置優(yōu)化點:

  • 對于經(jīng)常要訪問的路徑,可以使用精確匹配或^=修飾的匹配,可以避免進(jìn)行正則匹配檢查
  • 如果一定要用到正則表達(dá)式,可以把最經(jīng)常被訪問的location規(guī)則配置在最前面,因為正則匹配命中一個就不會繼續(xù)驗證后續(xù)的匹配規(guī)則

示例

假設(shè)有如下配置:

    server {
        listen 80 default_server;
        server_name _;
        # A
        location = / {
                return 200 "A";
        }

        # B
        location / {
                return 200 "B";
        }

        # C
        location /docs {
                return 200 "C";
        }

        # D
        location ^~ /imgs {
                return 200 "D";
        }
        
        # E
        location ~* \.(gif|jpg|png)$ {
                return 200 "E";
        }

        # F
        location ~ /a/.*$ {
                return 200 "F";
        }
    }
}

測試結(jié)果如下:

input output 說明
http://127.0.0.1 A 匹配到A跟B,精確匹配優(yōu)先級較高
http://127.0.0.1/test B 只匹配到B
http://127.0.0.1/docs/1 C 匹配到B跟C,C前綴比B長
http://127.0.0.1/docs/2.jpg E 匹配到B、C、E,正則匹配比普通前綴匹配優(yōu)先級高
http://127.0.0.1/imgs/1 D 只匹配到B、D,D前綴比B長
http://127.0.0.1/imgs/1.jpg D 匹配到B、D、E,由于D是最長匹配且有^~修飾符,所以不會再檢查正則匹配
http://127.0.0.1/docs/a/1 F 匹配到B、C、F

關(guān)于最后一條測試結(jié)果,需要注意的是,/a/.*$這個正則表達(dá)式,并不要求請求URI以/a開頭,這也是很容易疏漏的地方,若想匹配以/a開頭的請求,應(yīng)改為^/a/.*$,此時最后一條測試結(jié)果會變?yōu)镃

location @name的用法

@前綴可以用來定義一個命名的location,該location不處理正常的外部請求,一般用來供內(nèi)部重定向使用。它們不能嵌套,也不能包含嵌套的location。
例如:

location /try {
    try_files $uri $uri/ @name;
}

location /error {
    error_page 404 = @name;
    return 404;
}

location @name {
    return 200 "@name";
}

這時訪問/try或者/error都會返回"@name"

總結(jié)

本文主要介紹了nginx關(guān)于server_namelocation的配置以及匹配規(guī)則,并舉例說明。server_namelocation指令是nginx中非常重要的兩條指令,掌握這兩條指令對于我們配置nginx以及排查問題都是非常重要的,希望本文能幫到大家。

參考文檔

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容