Althttpd 是一款簡單的網頁伺服器,自 2004 年以來一直運行著 https://sqlite.dev.org.tw/ 網站。Althttpd 致力於簡潔性、安全性以及低資源使用率。
截至 2024 年,用於 sqlite.org 的 althttpd 執行個體每天處理超過 500,000 個 HTTP 請求(約每秒 5 或 6 個),每天傳輸約 200GB 的內容(約每秒 18 兆位元),運行在一台每月 40 美元的 Linode 伺服器上。這台機器的平均負載通常保持在 0.5 左右。大約 19% 的 HTTP 請求是傳送到各種 Fossil 原始碼儲存庫的 CGI 請求。
設計理念
Althttpd 通常從 xinetd 或 systemd 或類似程式啟動。每個連入連線都會啟動一個獨立的處理程序,該處理程序完全專注於服務該連線。單個 althttpd 處理程序將透過同一連線處理一個或多個 HTTP 請求。當連線關閉時,althttpd 處理程序就會結束。
Althttpd 也可以獨立運行。Althttpd 本身監聽連入 HTTP 請求的 80 連接埠(或連入 HTTPS 請求的 443 連接埠),然後複製自身的一個副本以處理每個連入連線。每個連線仍然使用單獨的處理程序來處理。唯一的區別是連線處理程序現在是由主 althttpd 執行個體啟動,而不是由 xinetd 或 systemd 啟動。
Althttpd 沒有設定檔。所有設定都使用幾個命令列引數來處理。這有助於保持設定的簡潔性,並減輕因網頁伺服器設定錯誤而引入安全漏洞的擔憂。
由於每個 althttpd 處理程序只需要服務單個連線,因此 althttpd 是單執行緒的。此外,每個處理程序僅在單個連線期間存活,這意味著 althttpd 不需要太擔心記憶體洩漏。這些設計因素有助於保持 althttpd 原始碼的簡潔性,從而便於安全審核和分析。
對於提供 TLS 連線,有兩個選項
可以在定義了
ENABLE_TLS
巨集並連結到-lssl -lcrypto
的情況下建置 althttpd,然後使用--cert fullchain.pem
和--pkey privkey.pem
旗標啟動。可以透過外部連線服務(例如 stunnel4)啟動 althttpd,並將
-https 1
旗標傳遞給 althttpd,以告知它透過該服務「間接」以 HTTPS 模式運行。
建議使用第一個選項(使用內建的 TLS)。
原始碼
althttpd 的完整原始碼包含在 單個 C 程式碼檔案 中,除了標準 C 程式庫之外沒有其他依賴項,如果選擇了 ENABLE_TLS 選項,則還需要 OpenSSL。此外,建置過程需要 VERSION.h
,它是由 包含的 Makefile 生成的。
althttpd 原始碼有大量的註釋且易於理解。針對特殊需求進行客製化應該相對容易。
要建置和安裝 althttpd,請選擇 Makefile 頂部列出的建置目標之一,然後執行
make THAT_TARGET
如果未提供目標,它將假設 libssl 可用,並嘗試建置名為 althttpd
和 althttpsd
的僅 HTTP 和 HTTPS 感知二進位檔案。要安裝它們,只需將它們移動到您選擇的目錄即可。
相應的建置規則非常簡單,因此可以輕鬆移植到其他建置基礎架構中。
SQLite 網站 使用 靜態建置,因此無需在伺服器上安裝 OpenSSL。
設定
在您的系統上執行 Althttpd 有許多方法。以下是一些變化:
以上並非詳盡的清單。基本概念是,每次在您的網頁伺服器連接埠(通常是連接埠 80 或 443)上出現新的通訊端連線時,您都會啟動一個新的 althttpd 程序副本以處理該連線。
完整的設定規格,包括所有命令列選項和配置選項的清單,位於 althttpd.c 原始程式碼檔案 中的一個大型標頭註釋內。
託管多個網域
Althttpd 使用每個 HTTP 請求的 HTTP_HOST 標頭來決定應該從哪裡提供內容。HTTP_HOST 標頭是促使網路瀏覽器發出 HTTP 請求的 URL 的網域名稱。Althttpd 會複製此名稱,將所有 ASCII 字母字元轉換為小寫,並將所有其他字元更改為「_」,然後附加「.website」。因此,例如,如果 HTTP_HOST 是「www.SQLite.org」,則轉換後的名稱將是「www_sqlite_org.website」。然後,Althttpd 會在其「-root」目錄中尋找具有該名稱的目錄,並從該目錄中傳遞內容。如果找不到此類目錄,或者 HTTP 請求省略了 HTTP_HOST 標頭,則會使用「default.website」目錄。因此,您只需擁有多個 *.website 資料夾,即可從同一台機器提供多個網站。在提供 SQLite 網站的 Linode 上,(上次統計)有 42 個 *.website 資料夾和符號連結,包括:
- www_sqlite_org.website
- default.website ← 符號連結至上一個
- fossil_scm_org.website
- pikchr_org.website
- www_cvstrac_org.website
- androwish_org.website
網站內容
在每個 *.website 資料夾中,一般檔案會作為靜態內容提供。可執行檔會以 CGI 方式執行。Althttpd 通常不會提供名稱以「.」或「-」開頭的檔案。這是一項安全功能 — 詳見下文。
GZip 內容壓縮
Althttpd 對伺服器端內容壓縮提供基本支援,這通常可將檔案的傳輸成本降低一半以上。它並未在 althttpd 中新增對壓縮程式庫的依賴性,而是依賴網站開發人員以壓縮和未壓縮的形式提供內容。
提供檔案時,如果用戶端表示支援 gzip 壓縮,並且找到具有相同名稱加上 .gz
副檔名的檔案,則會將檔案的 gzip 副本提供給用戶端,並附帶一個回應標頭,指示該檔案已 gzip 壓縮。對使用者而言,看起來就像提供的是壓縮後的原始請求檔案。然而,在底層,實際上提供的是不同的檔案。
請注意,此功能僅適用於靜態檔案,不適用於 CGI。
安全功能
為了防止惡意行為,Althttpd 對可提供的檔案名稱有限制。在請求 URI 中,所有非英數字元和「,-./:~」以外的字元都會轉換為單個「_」。此外,如果請求 URI 的任何路徑元素以「.」或「-」開頭,則 althttpd 一律會傳回 404 Not Found 錯誤。因此,只要檔名以「.」或「-」開頭,就可以安全地將輔助檔案(例如 CGI 使用的資料庫或其他內容)放在文件階層中。
當 althttpd 傳回 404 時,它會嘗試判斷請求是否為惡意的,如果是,它可以選擇性地暫時封鎖用戶端的 IP。
例外:雖然 althttpd 通常會針對任何路徑元素以「.」開頭的請求傳回 404 Not Found,但它允許 URI 以「/.well-known/」開頭的請求。「/.well-known/」以下的檔案或目錄名稱允許以「.」或「-」開頭(但不能以「..」開頭)。此例外是允許 LetsEncrypt 驗證網站所有權所必需的。
基本驗證
如果內容階層中任何位置出現名為「-auth」的檔案,則存取該目錄中的檔案需要 HTTP 基本驗證,其定義由「-auth」檔案的內容決定。「-auth」檔案僅適用於指定的目錄,不會遞迴套用至子目錄。「-auth」檔案是純文字檔,以行為單位。空行和以「#」開頭的行會被忽略。其他行的含義如下:
http-redirect
如果存在 http-redirect 行,則會導致所有 HTTP 請求重新導向至 HTTPS 請求。「-auth」檔案會依序讀取和處理,因此「http-redirect」行以下的行在處理 HTTP 請求時永遠不會被讀取或處理。
https-only
如果存在 https-only 行,則表示只允許 HTTPS 請求。任何 HTTP 請求都會導致 404 Not Found 錯誤。 https-only 行通常出現在 http-redirect 行之後。
realm 名稱
此格式的單行會建立基本驗證的「領域 (realm)」。網頁瀏覽器通常會在要求使用者名稱和密碼的對話方塊中顯示領域名稱作為標題。
user 名稱 登入帳號:密碼
有多個 user 行,每個有效使用者一行。「登入帳號:密碼」參數定義了使用者必須輸入以存取網站的使用者名稱和密碼。密碼是明文 — HTTP 基本驗證並非最安全的驗證機制。成功登入後,名稱會儲存在 REMOTE_USER 環境變數中,以便 CGI 腳本可以存取它。名稱和登入帳號通常相同,但也可以不同。
anyone
如果遇到「anyone」行,則表示允許任何請求,即使沒有提供使用者名稱和密碼。此行與「http-redirect」結合使用時非常有用,可以使所有普通的 HTTP 請求重新導向至 HTTPS,而無需登入憑證。
基本驗證範例
http://www.sqlite.org/ 網站在頂層目錄中包含一個「-auth」檔案,如下所示:
http-redirect anyone
該「-auth」檔案會導致所有 HTTP 請求重新導向至 HTTPS,而無需任何進一步的登入。(請嘗試:訪問 https://sqlite.dev.org.tw/ 並確認您已重新導向至 https://sqlite.dev.org.tw/。)
https://fossil-scm.org/private/ 有一個「-auth」檔案,如下所示:
realm Access To All Fossil Repositories http-redirect user drh drh:xxxxxxxxxxxxxxxx
當然,密碼並不是一連串的「x」字元。這展示了「-auth」檔案的典型用法。只要使用者使用 HTTPS 而不是 HTTP 輸入,就會授予單一使用者存取「private」子目錄中內容的權限。強烈建議所有基本驗證都使用「http-redirect」行,因為密碼包含在請求標頭中,如果請求是透過 HTTP 傳送,則可能會被惡意人士攔截和竊取。
記錄檔
如果在 althttpd 命令列中指定了 -logfile 選項,則會將一行附加到指定的檔案中,用於記錄每個 HTTP 請求。記錄檔採用 RFC4180 指定的逗號分隔值 (CSV) 格式。原始程式碼中有一段註釋說明了輸出行中每個欄位的含義。
記錄檔採用 CSV 格式,因此可以使用如下腳本輕鬆匯入 SQLite 以進行分析:
CREATE TABLE log( date TEXT, /* Timestamp */ ip TEXT, /* Source IP address */ url TEXT, /* Request URI */ ref TEXT, /* Referer */ code INT, /* Result code. ex: 200, 404 */ nIn INT, /* Bytes in request */ nOut INT, /* Bytes in reply */ t1 INT, t2 INT, /* Process time (user, system) milliseconds */ t3 INT, t4 INT, /* CGI script time (user, system) milliseconds */ t5 INT, /* Wall-clock time, milliseconds */ nreq INT, /* Sequence number of this request */ agent TEXT, /* User agent */ user TEXT, /* Remote user */ n INT, /* Bytes of url that are in SCRIPT_NAME */ lineno INT /* Source code line that generated log entry */ ); .mode csv .import httplog.csv log
-logfile 選項中的檔名可以包含由 strftime() 展開的基於時間的字元。因此,若要讓每天使用新的記錄檔,您可以使用如下內容:
-logfile /var/logs/althttpd/httplog-%Y%m%d.csv
封鎖客戶端 IP
如果 althttpd 中包含 --ipshun 目錄
選項,且「目錄」是 chroot jail 內可存取的絕對路徑(以「/」開頭),並且如果客戶端的 IP 位址在該目錄中顯示為檔案,則 althttpd 可能會返回 503 Service Unavailable,而不是處理請求。
如果檔案大小為零位元組,則一律返回 503。因此,您可以「touch」一個以 IP 位址命名的檔案來永久禁止該客戶端。
如果檔案大小為 N 位元組,則如果檔案的修改時間距離現在少於 300*N 秒,將會回傳 503 錯誤。換句話說,客戶端會被封鎖,封鎖時間為檔案每位元組 5 分鐘。
如果 althttpd 收到一個會導致 404 Not Found 的請求,並且在檢查 REQUEST_URI 後發現請求看起來可疑,就會自動建立封鎖檔案。例如,任何包含 /../ 的請求都會被視為駭客企圖。還會檢查其他常見的漏洞探測。這個漏洞探測清單可能會隨著經驗的累積而增長。
封鎖檔案會在每位元組 5 分鐘後自動刪除。
封鎖檔案最初的大小為 1 位元組。但如果封鎖到期,然後在每個封鎖檔案大小位元組 5 分鐘之前收到新的請求,則檔案會增加一個位元組,並且修改時間會被重置。
5 分鐘的封鎖時間可以在建置時透過傳遞 -DBANISH_TIME=N
參數來設定,其中 N 是以秒為單位的數字,預設值為 300。