Althttpd

Althttpd 網頁伺服器
登入

Althttpd 網頁伺服器

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 通常從 xinetdsystemd 或類似程式啟動。每個連入連線都會啟動一個獨立的處理程序,該處理程序完全專注於服務該連線。單個 althttpd 處理程序將透過同一連線處理一個或多個 HTTP 請求。當連線關閉時,althttpd 處理程序就會結束。

Althttpd 也可以獨立運行。Althttpd 本身監聽連入 HTTP 請求的 80 連接埠(或連入 HTTPS 請求的 443 連接埠),然後複製自身的一個副本以處理每個連入連線。每個連線仍然使用單獨的處理程序來處理。唯一的區別是連線處理程序現在是由主 althttpd 執行個體啟動,而不是由 xinetd 或 systemd 啟動。

Althttpd 沒有設定檔。所有設定都使用幾個命令列引數來處理。這有助於保持設定的簡潔性,並減輕因網頁伺服器設定錯誤而引入安全漏洞的擔憂。

由於每個 althttpd 處理程序只需要服務單個連線,因此 althttpd 是單執行緒的。此外,每個處理程序僅在單個連線期間存活,這意味著 althttpd 不需要太擔心記憶體洩漏。這些設計因素有助於保持 althttpd 原始碼的簡潔性,從而便於安全審核和分析。

對於提供 TLS 連線,有兩個選項

  1. 可以在定義了 ENABLE_TLS 巨集並連結到 -lssl -lcrypto 的情況下建置 althttpd,然後使用 --cert fullchain.pem--pkey privkey.pem 旗標啟動。

  2. 可以透過外部連線服務(例如 stunnel4)啟動 althttpd,並將 -https 1 旗標傳遞給 althttpd,以告知它透過該服務「間接」以 HTTPS 模式運行。

建議使用第一個選項(使用內建的 TLS)。

原始碼

althttpd 的完整原始碼包含在 單個 C 程式碼檔案 中,除了標準 C 程式庫之外沒有其他依賴項,如果選擇了 ENABLE_TLS 選項,則還需要 OpenSSL。此外,建置過程需要 VERSION.h,它是由 包含的 Makefile 生成的。

althttpd 原始碼有大量的註釋且易於理解。針對特殊需求進行客製化應該相對容易。

要建置和安裝 althttpd,請選擇 Makefile 頂部列出的建置目標之一,然後執行

 make THAT_TARGET

如果未提供目標,它將假設 libssl 可用,並嘗試建置名為 althttpdalthttpsd 的僅 HTTP 和 HTTPS 感知二進位檔案。要安裝它們,只需將它們移動到您選擇的目錄即可。

相應的建置規則非常簡單,因此可以輕鬆移植到其他建置基礎架構中。

SQLite 網站 使用 靜態建置,因此無需在伺服器上安裝 OpenSSL。

設定

在您的系統上執行 Althttpd 有許多方法。以下是一些變化:

  1. 使用 Systemd 執行 Althttpd
  2. 使用 Xinetd 執行 Althttpd
  3. 以獨立伺服器方式執行 Althttpd
  4. 使用 Stunnel4 執行 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 資料夾和符號連結,包括:

網站內容

在每個 *.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://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,而不是處理請求。

如果 althttpd 收到一個會導致 404 Not Found 的請求,並且在檢查 REQUEST_URI 後發現請求看起來可疑,就會自動建立封鎖檔案。例如,任何包含 /../ 的請求都會被視為駭客企圖。還會檢查其他常見的漏洞探測。這個漏洞探測清單可能會隨著經驗的累積而增長。

封鎖檔案會在每位元組 5 分鐘後自動刪除。

封鎖檔案最初的大小為 1 位元組。但如果封鎖到期,然後在每個封鎖檔案大小位元組 5 分鐘之前收到新的請求,則檔案會增加一個位元組,並且修改時間會被重置。

5 分鐘的封鎖時間可以在建置時透過傳遞 -DBANISH_TIME=N 參數來設定,其中 N 是以秒為單位的數字,預設值為 300。