如 (2-1) 統一資源識別符 (URI) 所述,
Client 會先透過 名稱解析服務 (ex: DNS),
找到 Server (或 代理、閘道、負載平衡器) 的 IP 位址,並藉此訪問:
然而,因早期 HTTP 的錯誤設計,Client 並不會送出 目標 URI !
若 單一主機 僅代表一個 網域名稱 那還好,那多個 呢 ?
本篇將說明 Server 與 代理 (proxy),
在沒有 目標 URI 的情況下,如何處理訊息路由。
Host 表頭欄位
有些人會問:
「Server 如何取得 client 輸入的 URL ?」
Ans:
Server 無法取得 目標 URI (target URI),是透過請求訊息中的:
Host 表頭欄位 -- 取得網域名稱 (domain name), 請求目標 (request-target) -- 取得路徑 (資源)。
以 重組 (reconstructs) 的方式,模擬出 目標 URI,
稱為 — — 有效請求 URI (Effective Request URI)。
以 PHP 為例:
$effectiveRequestURI = (isset($_SERVER['HTTPS']) ? "https://" : "https://")
. "$_SERVER[HTTP_HOST]"
. "$_SERVER[REQUEST_URI]";
使用者代理 (user agent) 送出請求訊息前,
需分離出 目標 URI 中的 authority 元件 為 Host 表頭欄位值 :
(即 host + [ ":" port ])
目標 URI =
"http(s):" "//" authority path-abempty [ "?" query ] [ "#" fragment ]
例如,將此 GET 請求 送至 源伺服器 (origin server) — https://www.example.com/pub/WWW/:
GET /pub/WWW/ HTTP/1.1
Host: example.com
...
Host 表頭欄位 是處理請求的關鍵資訊,
對所有 HTTP/1.1 請求訊息 (request message),皆 必須包含 Host 表頭欄位,
且建議做為 第一個 表頭欄位 (緊接在 request-line 之後),以利 Server 解析欄位。
若 請求訊息 (request message) 缺少了 Host 表頭欄位、
包含了多個 Host 表頭欄位 或 欄位值無效,
Server 需視請求 無效,並發送 400 (Bad Request) 的回應狀態碼。
如果 代理 (proxy) 接收到了 絕對形式 (absolute-form) 的 請求目標 (request-target),
需將 請求訊息 中的 Host 表頭欄位 視為 假的 忽略,
並以 絕對形式 (absolute-form) 提供的 URI 為主,生成新的 Host 表頭欄位。
虛擬主機 (Virtual Host)
Host 表頭欄位 是 HTTP/1.0 與 HTTP/1.1 最重要的改變之一,
解決了 HTTP/1.0 假定 「Server 與 網站 是 一對一」的錯誤,
因此,我們能標準的使用 虛擬主機 (Virtual Host) !
虛擬主機 (Virtual Host) 或稱 虛擬伺服器 (Virtual Server),
是指在同一機器上運行多個網站,
最適合貧窮如我,只租的起一台 Server 的人 😭。
又分為 基於網域名稱 (Name-based) 與 基於 IP 位址 (IP-based)。
基於網域名稱 (Name-based)
基於網域名稱 (Name-based),是最常見的 虛擬主機 (Virtual Host) 形式,是指:
在同一機器、IP 位址,架設 多個網域名稱 (例如 example.com 與 abc.com) 的 網站,
當然,也包含子網域啦 (例如 blog.example.com 與 mail.example.com) !
以常見的 Web Server 為例:
Nginx (1.10) 可透過 網站設定檔 中 (通常位於 /etc/nginx/sites-available) 的,
server_name 指令 — 設定對應的網域名稱;root 指令 — 設定網站根目錄:
server {
listen 80 default_server;
server_name example.com www.example.net;
root /var/www/domain;
...
}
server {
listen 80;
server_name abc.com;
root /var/www/otherdomain;
...
}
若使用的是新的網站設定檔,需記得使用 ln -s
指令,
建立 符號連結 (symbolic link) — 捷徑:
sudo ln -s /etc/nginx/sites-available/新網站設定檔 /etc/nginx/sites-enabled
並重啟 nginx:
sudo systemctl restart nginx
Apache (2.4) 也是大同小異:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.net;
DocumentRoot "/www/domain"
</VirtualHost>
<VirtualHost *:80>
ServerName abc.com
DocumentRoot "/www/otherdomain"
</VirtualHost>
基於 IP 位址 (IP-based)
基於 IP 位址 (IP-based),是指:
在同一機器、不同的 IP 位址,架設 多個網站 (例如 192.168.1.1:80 與 172.20.30.40:80),
當然,也包含 埠號 (TCP port) 啦 (例如 192.168.1.1:7577 與 192.168.1.1:9487) !
以常見的 Web Server 為例:
Nginx (1.10) 可透過 網站設定檔 中 (通常位於 /etc/nginx/sites-available) 的,
listen 指令 — 設定監聽的 IP 位址;root 指令 — 設定網站根目錄:
server {
listen 192.168.1.1:80 default_server;
server_name example.com www.example.net;
root /var/www/domain;
...
}
server {
listen 172.20.30.40:9487;
server_name abc.com;
root /var/www/otherdomain;
...
}
同樣,若使用的是新的網站設定檔,需記得使用 ln -s
指令,
建立 符號連結 (symbolic link) — 捷徑:
sudo ln -s /etc/nginx/sites-available/新網站設定檔 /etc/nginx/sites-enabled
並重啟 nginx:
sudo systemctl restart nginx
Apache (2.4) 也是大同小異:
<VirtualHost 192.168.1.1:80>
ServerName example.com
ServerAlias www.example.net;
DocumentRoot "/www/domain"
</VirtualHost>
<VirtualHost 172.20.30.40:9487>
ServerName abc.com
DocumentRoot "/www/otherdomain"
</VirtualHost>