Apache HTTP 伺服器 2.4 版
可用語言: en
本文件尚未更新以反映 Apache HTTP 伺服器 2.0 版中的變更。某些資訊可能仍然相關,但請謹慎使用。
以下是一些關於 Apache API 和您必須處理的資料結構等的注意事項。它們還沒有完全完成,但希望它們能幫助您掌握方向。請記住,API 仍有可能隨著我們獲得經驗而改變。(請參閱 TODO 文件以了解可能會發生什麼變化)。但是,將模組適應任何做出的更改都很容易。(我們要適應的模組比您多)。
這裡有一些關於一般教學風格的注意事項。為了簡潔起見,這裡的所有結構聲明都是不完整的——實際的結構有更多我沒有告訴您的欄位。在大多數情況下,這些欄位是保留給伺服器核心的某個組件或另一個組件的,並且模組在更改時應謹慎。但是,在某些情況下,它們確實是我還沒有時間處理的事情。歡迎來到最前沿。
最後,這裡是一個大綱,讓您大致了解接下來會發生什麼,以及發生的順序
我們首先概述 API 背後的基本概念,以及它們如何在代碼中體現。
Apache 將請求處理分解成一系列步驟,或多或少與 Netscape 伺服器 API 的方式相同(儘管此 API 的階段比 NetSite 多一些,作為我認為將來可能有用的東西的鉤子)。這些是
SetEnv
,這些擴展在其他地方不太合適。這些階段的處理方式是查看一系列的_模組_,查看每個模組是否有該階段的處理器,如果有則嘗試調用它。處理器通常可以執行以下三件事之一
OK
來表示它已完成處理。DECLINED
。在這種情況下,伺服器的行為在所有方面都好像處理器根本不存在一樣。大多數階段都由處理它們的第一個模組終止;但是,對於日誌記錄、“修復”和非訪問身份驗證檢查,所有處理器始終運行(除非出現錯誤)。此外,回應階段的獨特之處在於,模組可以通過以請求對象的 MIME 類型為鍵的調度表為其聲明多個處理器。模組可以聲明一個可以處理_任何_請求的回應階段處理器,方法是為其指定鍵 */*
(即通配符 MIME 類型規範)。但是,只有當伺服器已經嘗試並且未能為所請求對象的 MIME 類型找到更具體的回應處理器時(或者不存在,或者它們都拒絕了),才會調用通配符處理器。
處理器本身是一個參數的函數(一個 request_rec
結構。見下文),它返回一個整數,如上所述。
此時,我們需要解釋模組的結構。我們的候選者將是更混亂的模組之一,CGI 模組——它同時處理 CGI 腳本和 ScriptAlias
配置文件命令。它實際上比大多數模組複雜得多,但如果我們只打算舉一個例子,那不妨舉一個到處都有的例子。
讓我們從處理器開始。為了處理 CGI 腳本,模組為它們聲明了一個回應處理器。由於 ScriptAlias
,它還具有名稱轉換階段的處理器(用於識別 ScriptAlias
ed URI)、類型檢查階段的處理器(任何 ScriptAlias
ed 請求都鍵入為 CGI 腳本)。
模組需要維護一些每個(虛擬)伺服器的資訊,即生效的 ScriptAlias
es;因此,模組結構包含指向構建這些結構的函數的指針,以及指向合併其中兩個函數的指針(以防主伺服器和虛擬伺服器都聲明了 ScriptAlias
es)。
最後,此模組包含用於處理 ScriptAlias
命令本身的代碼。此特定模組僅聲明一個命令,但可能還有更多命令,因此模組具有_命令表_,這些命令表聲明它們的命令,並描述允許它們在哪裡使用,以及如何調用它們。
關於其中一些命令的參數的聲明類型的最後說明:pool
是指向_資源池_結構的指針;伺服器使用這些結構來跟踪已分配的內存、已打開的文件等,以服務於特定請求,或處理配置自身的過程。這樣,當請求結束時(或者,對於配置池,當伺服器重新啟動時),可以_批量_釋放內存並關閉文件,而無需任何人編寫顯式代碼來跟踪它們並處置它們。此外,cmd_parms
結構包含有關正在讀取的配置文件的各種資訊,以及其他狀態資訊,這些資訊有時對處理配置文件命令(例如 ScriptAlias
)的函數很有用。言歸正傳,模組本身
/* 處理器聲明。 */
int translate_scriptalias (request_rec *);
int type_scriptalias (request_rec *);
int cgi_handler (request_rec *);
/* 回應階段的輔助調度表
* 處理器,按 MIME 類型 */
handler_rec cgi_handlers[] = {
{ "application/x-httpd-cgi", cgi_handler },
{ NULL }
};
/* 操作的例程聲明
* 模組的配置資訊。請注意,這些是
* 返回並作為 void * 傳遞;伺服器
* 核心跟踪它們,但它沒有,也不能,
* 知道它們的內部結構。
*/
void *make_cgi_server_config (pool *);
void *merge_cgi_server_config (pool *, void *, void *);
/* 處理配置文件命令的例程聲明 */
extern char *script_alias(cmd_parms *, void *per_dir_config, char *fake, char *real);
command_rec cgi_cmds[] = {
{ "ScriptAlias", script_alias, NULL, RSRC_CONF, TAKE2,
"假名和真實姓名"},
{ NULL }
};
module cgi_module = {
STANDARD_MODULE_STUFF, NULL, /* initializer */ NULL, /* dir config creator */ NULL, /* dir merger */ make_cgi_server_config, /* server config */ merge_cgi_server_config, /* merge server config */ cgi_cmds, /* command table */ cgi_handlers, /* handlers */ translate_scriptalias, /* filename translation */ NULL, /* check_user_id */ NULL, /* check auth */ NULL, /* check access */ type_scriptalias, /* type_checker */ NULL, /* fixups */ NULL, /* logger */ NULL /* header parser */ };
處理器的唯一參數是 request_rec
結構。此結構描述了代表客戶端向伺服器發出的特定請求。在大多數情況下,與客戶端的每個連接只會生成一個 request_rec
結構。
request_rec
結構簡述request_rec
包含指向資源池的指針,當伺服器完成處理請求時將清除該資源池;指向包含每個伺服器和每個連接的資訊的結構的指針,以及最重要的,有關請求本身的資訊。
最重要的此類資訊是一組小的字串,描述所請求對象的屬性,包括其 URI、文件名、內容類型和內容編碼(這些分別由處理請求的轉換和類型檢查處理器填充)。
其他常用的數據項是提供客戶端原始請求上的 MIME 標頭的表、要隨回應發送回的 MIME 標頭(模組可以隨意添加到其中)以及在服務請求過程中生成的任何子進程的環境變量。這些表使用 ap_table_get
和 ap_table_set
例程進行操作。
請注意,_不能_使用 ap_table_*()
例程由模組內容處理器設置 Content-type
標頭值。相反,它是通過將 request_rec
結構中的 content_type
字段指向適當的字串來設置的。_例如_,
r->content_type = "text/html";
最後,有兩個數據結構的指針,它們又指向每個模組的配置結構。具體來說,這些指針指向模組為描述其在給定目錄(通過 .htaccess
文件或 <Directory>
部分)中的配置方式而構建的數據結構,以及指向其在服務請求過程中構建的私有數據的指針(因此,模組對一個階段的處理器可以將“註釋”傳遞給它們對其他階段的處理器)。在 request_rec
指向的 server_rec
數據結構中還有另一個這樣的配置向量,它包含每個(虛擬)伺服器的配置數據。
以下是一個簡略的聲明,給出了最常用的字段
struct request_rec {
pool *pool;
conn_rec *connection;
server_rec *server;
/* 正在請求什麼對象 */
char *uri;
char *filename;
char *path_info;
char *args; /* QUERY_ARGS, if any */ struct stat finfo; /* Set by server core; * st_mode set to zero if no such file */
char *content_type;
char *content_encoding;
/* MIME 標頭環境,輸入和輸出。還有,
* 一個數組,其中包含要
* 傳遞給子進程的環境變量,因此人們可以編寫
* 模組來添加到該環境中。
*
* headers_out 和
* err_headers_out 之間的區別在於,即使在
* 出現錯誤時也會打印後者,並且在內部
* 重定向中保持不變(因此為
* ErrorDocument
處理器打印的標頭將具有它們)。
*/
表 *headers_in;
表 *headers_out;
表 *err_headers_out;
表 *subprocess_env;
/* 有關請求本身的信息... */
int header_only; /* HEAD request, as opposed to GET */ char *protocol; /* Protocol, as given to us, or HTTP/0.9 */ char *method; /* GET, HEAD, POST, etc. */ int method_number; /* M_GET, M_POST, etc. */
/* 用於記錄的信息 */
字符 *the_request;
整數 bytes_sent;
/* 模組可以設置的標誌,表示
* 返回的數據是易變的,並且應該告知客戶端
* 不要緩存它。
*/
整數 no_cache;
/* 其他各種可能會改變的配置信息
* 與 .htaccess 文件一起
* 這些是配置向量,每個模組有一個 void*
* 指針(指向的內容
* 是模組的業務)。
*/
void *per_dir_config; /* Options set in config files, etc. */ void *request_config; /* Notes on *this* request */
};
request_rec
結構的來源大多數 request_rec
結構是通過從客戶端讀取 HTTP 請求並填充字段來構建的。但是,也有一些例外
*.var
文件)或返回本地「位置:」的 CGI 腳本,則用戶請求的資源最終將由客戶端最初提供的 URI 以外的其他 URI 定位。在這種情況下,伺服器會執行*內部重定向*,為新的 URI 構造一個新的 request_rec
,並幾乎像客戶端直接請求新的 URI 一樣處理它。ErrorDocument
在範圍內,則會使用相同的內部重定向機制。最後,處理程序偶爾需要調查如果運行其他請求會發生什麼情況。例如,目錄索引模組需要知道將為每個目錄條目的請求分配什麼 MIME 類型,以便確定要使用的圖標。
此類處理程序可以使用函數 ap_sub_req_lookup_file
、ap_sub_req_lookup_uri
和 ap_sub_req_method_uri
構造*子請求*;這些函數會構造一個新的 request_rec
結構,並按預期處理它,直到實際發送響應為止。(如果子請求針對與原始請求位於同一目錄中的文件,則這些函數會跳過訪問檢查)。
(伺服器端包含的工作原理是構建子請求,然後通過函數 ap_run_sub_req
實際調用它們的響應處理程序)。
如上所述,每個處理程序在被調用以處理特定的 request_rec
時,都必須返回一個 int
來指示發生了什麼。可以是
OK
-- 請求已成功處理。這可能會或可能不會終止階段。DECLINED
-- 不存在錯誤情況,但模組拒絕處理該階段;伺服器嘗試找到另一個。請注意,如果返回的錯誤代碼是 REDIRECT
,則模組應在請求的 headers_out
中放置一個 Location
,以指示應將客戶端重定向*到*哪裡。
大多數階段的處理程序只需在 request_rec
結構中設置幾個字段即可完成工作(或者,對於訪問檢查器,只需返回正確的錯誤代碼即可)。但是,響應處理程序必須實際向客戶端發送請求。
它們應該首先使用 ap_send_http_header
函數發送 HTTP 響應標頭。(您不必為 HTTP/0.9 請求跳過發送標頭做任何特殊處理;該函數會自行判斷它不應該做任何事情)。如果請求標記為 header_only
,則這就是它們應該做的所有事情;它們應該在那之後返回,而無需嘗試任何進一步的輸出。
否則,它們應該生成一個請求正文,以適當的方式響應客戶端。用於此目的的原語是 ap_rputc
和 ap_rprintf
,用於內部生成的輸出,以及 ap_send_fd
,用於將某些 FILE *
的內容直接複製到客戶端。
此時,您應該或多或少地理解以下代碼段,它是處理沒有更具體處理程序的 GET
請求的處理程序;它還顯示了如何在特定響應處理程序中處理條件 GET
(如果需要的話)-- ap_set_last_modified
根據客戶端提供的 If-modified-since
值(如果有)進行檢查,並返回適當的代碼(如果非零,則為 USE_LOCAL_COPY)。對於 ap_set_content_length
沒有類似的考慮因素,但為了對稱性,它會返回一個錯誤代碼。
int default_handler (request_rec *r)
{
int errstatus;
FILE *f;
if (r->method_number != M_GET) return DECLINED;
if (r->finfo.st_mode == 0) return NOT_FOUND;
if ((errstatus = ap_set_content_length (r, r->finfo.st_size))
|| (errstatus = ap_set_last_modified (r, r->finfo.st_mtime)))
return errstatus;
f = fopen (r->filename, "r");
if (f == NULL) {
log_reason("文件權限拒絕伺服器訪問", r->filename, r);
return FORBIDDEN;
}
register_timeout ("send", r);
ap_send_http_header (r);
if (!r->header_only) send_fd (f, r);
ap_pfclose (r->pool, f);
return OK;
}
最後,如果所有這些都太具有挑戰性,則有一些方法可以解決。首先,如上所示,尚未產生任何輸出的響應處理程序可以簡單地返回一個錯誤代碼,在這種情況下,伺服器將自動產生一個錯誤響應。其次,它可以通過調用 ap_internal_redirect
轉移到其他處理程序,這就是上面討論的內部重定向機制的調用方式。內部重定向的響應處理程序應始終返回 OK
。
(從*不是*響應處理程序的處理程序調用 ap_internal_redirect
將導致嚴重的混亂)。
應該在這裡詳細討論的事情
ap_auth_type
、ap_auth_name
和 ap_requires
。ap_get_basic_auth_pw
,它自動設置 connection->user
結構字段,以及 ap_note_basic_auth_failure
,它安排將適當的 WWW-Authenticate:
標頭發送回去)。當請求內部重定向時,會出現要記錄什麼的問題。Apache 通過將整個重定向鏈捆綁到一個 request_rec
結構列表中來處理此問題,這些結構通過 r->prev
和 r->next
指針鏈接在一起。在這種情況下傳遞給日誌記錄處理程序的 request_rec
是最初為客戶端的初始請求構建的那個;請注意,bytes_sent
字段僅在鏈中的最後一個請求(實際發送響應的那個)中才是正確的。
編寫和設計伺服器池伺服器的一個問題是如何防止洩漏,即分配資源(內存、打開的文件*等*),而隨後沒有釋放它們。資源池機制旨在通過允許以*自動*在伺服器使用完資源時釋放它們的方式分配資源,從而輕鬆防止這種情況的發生。
其工作原理如下:分配的內存、打開的文件*等*,用於處理特定請求的資源都與為該請求分配的*資源池*綁定。池是一種數據結構,它本身跟踪相關資源。
當請求被處理後,池就會被*清除*。此時,與其關聯的所有內存都將被釋放以供重用,與其關聯的所有文件都將被關閉,並且與該池關聯的任何其他清理函數都將運行。完成此操作後,我們可以確信綁定到池的所有資源都已釋放,並且沒有任何資源洩漏。
伺服器重新啟動以及為每個伺服器配置分配內存和資源的方式類似。有一個*配置池*,它跟踪在讀取伺服器配置文件和處理其中的命令時分配的資源(例如,為每個伺服器模組配置分配的內存、打開的日誌文件和其他文件,等等)。當伺服器重新啟動並必須重新讀取配置文件時,配置池將被清除,因此上次讀取它們時佔用的內存和文件描述符將被釋放以供重用。
應該注意的是,除了像日誌記錄處理程序這樣的情況外,通常不需要使用池機制,在這種情況下,您確實需要註冊清理以確保在伺服器重新啟動時關閉日誌文件(最簡單的方法是使用 ap_pfopen
函數,該函數還安排在任何子進程(例如 CGI 腳本)被 exec
之前關閉底層文件描述符),或者在您使用超時機制的情況下(這裡甚至還沒有記錄)。但是,使用它有兩個好處:分配給池的資源永遠不會洩漏(即使您分配了一個臨時字符串,然後忘記了它);此外,對於內存分配,ap_palloc
通常比 malloc
快。
我們首先描述如何將內存分配給池,然後討論資源池機制如何跟踪其他資源。
通過調用 ap_palloc
函數將內存分配給池,該函數接受兩個參數,一個是指向資源池結構的指針,另一個是要分配的內存量(以 char
為單位)。在處理請求的處理程序中,獲取資源池結構的最常用方法是查看相關 request_rec
的 pool
插槽;因此,模組代碼中會重複出現以下習慣用法
int my_handler(request_rec *r)
{
struct my_structure *foo;
...
foo = (foo *)ap_palloc (r->pool, sizeof(my_structure));
}
請注意,*沒有 ap_pfree
* -- 只有在關聯的資源池被清除時,才會釋放 ap_palloc
分配的內存。這意味著 ap_palloc
不必像 malloc()
那樣進行那麼多會計處理;在典型情況下,它所做的只是向上捨入大小、增加一個指針並進行範圍檢查。
(它還增加了大量使用 ap_palloc
可能導致伺服器進程過大的可能性。有兩種方法可以解決這個問題,下面將對其進行討論;簡而言之,您可以使用 malloc
,並嘗試確保所有內存都被顯式 free
d,或者您可以分配主池的子池,在子池中分配內存,並定期清除它。後一種技術將在下面的子池部分中討論,並用於目錄索引代碼中,以避免在列出包含數千個文件的目錄時分配過多的存儲空間)。
有一些函數可以分配已初始化的內存,並且經常很有用。函數 ap_pcalloc
具有與 ap_palloc
相同的接口,但在返回之前清除它分配的內存。函數 ap_pstrdup
接受一個資源池和一個 char *
作為參數,並為指針指向的字符串的副本分配內存,返回指向副本的指針。最後,ap_pstrcat
是一個可變參數樣式函數,它接受一個指向資源池的指針,以及至少兩個 char *
參數,其中最後一個必須是 NULL
。它分配足夠的內存來容納每個字符串的副本,作為一個單元;例如
ap_pstrcat (r->pool, "foo", "/", "bar", NULL);
返回一個指向 8 字節內存的指針,初始化為 "foo/bar"
。
一個資源池實際上是由它的生命週期定義的。在 http_main 中有一些靜態資源池,它們會在適當的時候作為參數傳遞給各種非 http_main 函數。它們是:
permanent_pool
pconf
ptemp
pchild
ptrans
r->pool
對於人們所做的幾乎所有事情,r->pool
都是要使用的資源池。但是您可以看到其他生命週期(例如 pchild)如何對某些模組有用... 例如,需要為每個子進程打開一次數據庫連接並希望在子進程死亡時清理它的模組。
您還可以查看某些錯誤是如何表現出來的,例如將 connection->user
設置為 r->pool
中的值 - 在這種情況下,連接在 ptrans
的生命週期內存在,這比 r->pool
長(特別是如果 r->pool
是子請求!)。因此,正確的做法是從 connection->pool
分配。
並且在 mod_include
/ mod_cgi
中還有一個有趣的錯誤。您會在其中看到它們進行此測試以決定是使用 r->pool
還是 r->main->pool
。在這種情況下,他們正在註冊以進行清理的資源是一個子進程。如果它在 r->pool
中註冊,那麼代碼將在子請求完成時對子進程執行 wait()
。使用 mod_include
,這可能是任何舊的 #include
,並且延遲可能長達 3 秒... 而且經常發生。相反,子進程在 r->main->pool
中註冊,這會導致在整個請求完成時對其進行清理 - 即在輸出已發送到客戶端並記錄日誌之後。
如上所述,資源池還用於跟踪除內存之外的其他類型的資源。最常見的是打開的文件。通常用於此目的的例程是 ap_pfopen
,它接受一個資源池和兩個字符串作為參數;這些字符串與 fopen
的典型參數相同,例如:
...
FILE *f = ap_pfopen (r->pool, r->filename, "r");
if (f == NULL) { ... } else { ... }
還有一個 ap_popenf
例程,它與較低級別的 open
系統調用相平行。這兩個例程都安排在清除相關資源池時關閉文件。
與內存的情況不同,確實有函數可以關閉使用 ap_pfopen
和 ap_popenf
分配的文件,即 ap_pfclose
和 ap_pclosef
。(這是因為在許多系統上,單個進程可以打開的文件數量非常有限)。使用這些函數來關閉使用 ap_pfopen
和 ap_popenf
分配的文件非常重要,因為否則可能會在 Linux 等系統上導致致命錯誤,如果同一個 FILE*
被關閉多次,這些系統會做出不良反應。
(使用 close
函數不是強制性的,因為無論如何文件最終都會被關閉,但是您應該在模組正在打開或可能打開大量文件的情況下考慮使用它)。
更多文字在此處。根據實現文件內容的原語描述清理原語;還有 spawn_process
。
資源池清理一直存在,直到調用 clear_pool()
:clear_pool(a)
遞歸地在 a
的所有子資源池上調用 destroy_pool()
;然後為 a
調用所有清理;然後釋放 a
的所有內存。 destroy_pool(a)
調用 clear_pool(a)
,然後釋放資源池結構本身。也就是說,clear_pool(a)
不會刪除 a
,它只是釋放所有資源,您可以立即開始再次使用它。
在極少數情況下,過於自由地使用 ap_palloc()
和相關原語可能會導致不必要的資源分配浪費。您可以通過創建一個*子資源池*來處理這種情況,在子資源池而不是主資源池中進行分配,並清除或銷毀子資源池,這將釋放與其關聯的資源。(這確實是一種罕見的情況;它在標準模組集中出現的唯一情況是在列出目錄時,並且僅限於*非常*大的目錄。不必要地使用這裡討論的原語會使您的代碼變得非常混亂,而收穫甚微)。
創建子資源池的原語是 ap_make_sub_pool
,它接受另一個資源池(父資源池)作為參數。當清除主資源池時,子資源池將被銷毀。也可以隨時通過調用函數 ap_clear_pool
和 ap_destroy_pool
分別清除或銷毀子資源池。(區別在於 ap_clear_pool
釋放與資源池關聯的資源,而 ap_destroy_pool
還釋放資源池本身。在前一種情況下,您可以在資源池中分配新資源,並再次清除它,依此類推;在後一種情況下,它就消失了)。
最後一點 - 子請求擁有自己的資源池,它們是主請求的資源池的子資源池。回收與您分配的子請求(使用 ap_sub_req_...
函數)關聯的資源的禮貌方法是 ap_destroy_sub_req
,它釋放資源池。在調用此函數之前,請確保將您關心的任何可能在子請求的資源池中分配的內容複製到不太容易更改的位置(例如,其 request_rec
結構中的文件名)。
(同樣,在大多數情況下,您不應該覺得有義務調用此函數;對於典型的子請求,只分配了大約 2K 的內存,並且在清除主請求池時無論如何都會釋放它。只有當您為一個主請求分配許多子請求時,才應該認真考慮 ap_destroy_...
函數)。
此服務器的一個設計目標是保持與 NCSA 1.3 服務器的外部兼容性 --- 也就是說,讀取相同的配置文件,正確處理其中的所有指令,並且通常作為 NCSA 的直接替換。另一方面,另一個設計目標是將服務器的盡可能多的功能移至與單片服務器核心盡可能少的模組中。協調這些目標的唯一方法是將大多數命令的處理從中央服務器移至模組。
但是,僅僅為模組提供命令表不足以將它們與服務器核心完全分離。服務器必須記住這些命令以便稍後對其採取行動。這涉及維護模組私有的數據,這些數據可以是每個服務器或每個目錄的。大多數東西都是每個目錄的,特別包括訪問控制和授權信息,但也包括如何從後綴確定文件類型的信息,這些信息可以通過 AddType
和 ForceType
指令修改,等等。總的來說,指導原則是任何*可以*通過目錄配置的東西都應該這樣做;每個服務器的信息通常在標準模組集中用於諸如 Alias
es 和 Redirect
s 之類的信息,這些信息在請求與底層文件系統中的特定位置綁定之前起作用。
模擬 NCSA 服務器的另一個要求是能夠處理每個目錄的配置文件,通常稱為 .htaccess
文件,儘管即使在 NCSA 服務器中,它們也可能包含與訪問控制完全無關的指令。因此,在 URI -> 文件名轉換之後但在執行任何其他階段之前,服務器會沿著底層文件系統的目錄層次結構向下走,遵循轉換後的路徑名,以讀取可能存在的任何 .htaccess
文件。然後,讀入的信息必須與來自服務器自身配置文件的適用信息*合併*(來自 access.conf
中的 <Directory>
部分,或來自 srm.conf
中的默認值,後者實際上在大多數情況下的行為幾乎與 <Directory />
完全相同)。
最後,在處理了涉及讀取 .htaccess
文件的請求之後,我們需要丟棄為處理它們而分配的存儲空間。這與解決其他任何地方出現類似問題的方法相同,方法是將這些結構綁定到每個事務的資源池。
讓我們看看這一切是如何在 mod_mime.c
中實現的,它定義了文件類型處理程序,該處理程序模擬 NCSA 服務器從後綴確定文件類型的行為。我們在這裡要看的是實現 AddType
和 AddEncoding
命令的代碼。這些命令可以出現在 .htaccess
文件中,因此必須在模組的私有每個目錄數據中處理,實際上,它包含兩個用於 MIME 類型和編碼信息的單獨表,聲明如下
typedef struct { table *forced_types; /* Additional AddTyped stuff */ table *encoding_types; /* Added with AddEncoding... */ } mime_dir_config;
當服務器正在讀取配置文件或包含 MIME 模組命令之一的 <Directory>
部分時,它需要創建一個 mime_dir_config
結構,以便這些命令可以對其進行操作。它通過調用它在模組的“創建每個目錄配置槽”中找到的函數來做到這一點,該函數帶有兩個參數:此配置信息適用的目錄的名稱(對於 srm.conf
,則為 NULL
)和一個指向應在其中進行分配的資源池的指針。
(如果我們正在讀取 .htaccess
文件,則該資源池是請求的每個請求資源池;否則,它是用於配置數據並在重新啟動時清除的資源池。無論哪種方式,重要的是要在清除池時消失正在創建的結構,如有必要,請在池上註冊清理)。
對於 MIME 模組,每個目錄配置創建函數僅 ap_palloc
s 上面的結構,並創建幾個表來填充它。它看起來像這樣
void *create_mime_dir_config (pool *p, char *dummy)
{
mime_dir_config *new =
(mime_dir_config *) ap_palloc (p, sizeof(mime_dir_config));
new->forced_types = ap_make_table (p, 4);
new->encoding_types = ap_make_table (p, 4);
return new;
}
現在,假設我們剛讀取了一個 .htaccess
檔案。我們已經有了階層中上一層目錄的目錄配置結構。如果我們剛讀取的 .htaccess
檔案中沒有任何 AddType
或 AddEncoding
命令,則其 MIME 模組的目錄配置結構仍然有效,我們可以直接使用它。否則,我們需要以某種方式合併這兩個結構。
為此,伺服器會呼叫模組的目錄配置合併函式(如果有的話)。該函式接受三個參數:要合併的兩個結構,以及用於分配結果的資源池。對於 MIME 模組,只需用父目錄配置結構中的表格覆蓋新的目錄配置結構中的表格即可。
void *merge_mime_dir_configs (pool *p, void *parent_dirv, void *subdirv)
{
mime_dir_config *parent_dir = (mime_dir_config *)parent_dirv;
mime_dir_config *subdir = (mime_dir_config *)subdirv;
mime_dir_config *new =
(mime_dir_config *)ap_palloc (p, sizeof(mime_dir_config));
new->forced_types = ap_overlay_tables (p, subdir->forced_types,
parent_dir->forced_types);
new->encoding_types = ap_overlay_tables (p, subdir->encoding_types,
parent_dir->encoding_types);
return new;
}
請注意,如果沒有目錄合併函式,伺服器將只使用子目錄的配置資訊,而忽略父目錄的配置資訊。對於某些模組,這樣做是可行的(*例如*,對於包含模組,其目錄配置資訊僅包含 XBITHACK
的狀態),對於這些模組,您可以不宣告它,並將模組本身中相應的結構槽保留為 NULL
。
現在我們有了這些結構,我們需要能夠弄清楚如何填充它們。這涉及到處理實際的 AddType
和 AddEncoding
命令。為了找到命令,伺服器會查看模組的命令表。該表包含有關命令接受多少個參數、以何種格式、允許在哪裡出現等資訊。這些資訊足以讓伺服器使用預先解析的參數呼叫大多數命令處理函式。話不多說,讓我們來看看 AddType
命令處理程式,它看起來像這樣(AddEncoding
命令看起來基本相同,這裡就不展示了)。
char *add_type(cmd_parms *cmd, mime_dir_config *m, char *ct, char *ext)
{
if (*ext == '.') ++ext;
ap_table_set (m->forced_types, ext, ct);
return NULL;
}
這個命令處理程式異常簡單。如您所見,它接受四個參數,其中兩個是預先解析的參數,第三個是相關模組的目錄配置結構,第四個是指向 cmd_parms
結構的指標。該結構包含許多對某些(但不是所有)命令經常有用的參數,包括資源池(可以從中分配記憶體,並且清理應該與之綁定)以及正在配置的(虛擬)伺服器,如果需要,可以從中獲取模組的伺服器配置資料。
這個特定的命令處理程式異常簡單的另一個方面是它沒有可能遇到的錯誤條件。如果有的話,它可以返回一個錯誤訊息而不是 NULL
;這會導致在伺服器的 stderr
上列印出一條錯誤訊息,然後快速退出,如果它在主配置檔案中的話;對於 .htaccess
檔案,語法錯誤會記錄在伺服器錯誤日誌中(以及錯誤來源的指示),並且請求會以伺服器錯誤回應(HTTP 錯誤狀態,代碼 500)被拒絕。
MIME 模組的命令表中有這些命令的條目,如下所示:
command_rec mime_cmds[] = {
{ "AddType", add_type, NULL, OR_FILEINFO, TAKE2,
"MIME 類型,後跟檔案副檔名" },
{ "AddEncoding", add_encoding, NULL, OR_FILEINFO, TAKE2,
"編碼(*例如*,gzip),後跟檔案副檔名" },
{ NULL }
};
這些表中的條目是:
(void *)
指標,它在 cmd_parms
結構中傳遞給命令處理程式 - 如果許多類似的命令由同一個函式處理,這將非常有用。AllowOverride
選項都有一個對應的遮罩位元,還有一個額外的遮罩位元 RSRC_CONF
,表示該命令可能出現在伺服器自己的配置檔案中,但*不能*出現在任何 .htaccess
檔案中。TAKE2
表示兩個預先解析的參數。其他選項包括 TAKE1
(表示一個預先解析的參數)、FLAG
(表示參數應該是 On
或 Off
,並作為布林值標誌傳遞)和 RAW_ARGS
(導致伺服器將原始的、未解析的參數(命令名稱本身除外)傳遞給命令)。還有 ITERATE
,這意味著處理程式看起來與 TAKE1
相同,但如果存在多個參數,則應多次呼叫它,最後是 ITERATE2
,這表示命令處理程式看起來像 TAKE2
,但如果存在更多參數,則應多次呼叫它,並將第一個參數保持不變。NULL
)。最後,設定好所有這些之後,我們必須使用它。這最終是在模組的處理程式中完成的,特別是針對其檔案類型處理程式,它或多或少看起來像這樣;請注意,目錄配置結構是使用 ap_get_module_config
函式從 request_rec
的目錄配置向量中提取的。
int find_ct(request_rec *r)
{
int i;
char *fn = ap_pstrdup (r->pool, r->filename);
mime_dir_config *conf = (mime_dir_config *)
ap_get_module_config(r->per_dir_config, &mime_module);
char *type;
if (S_ISDIR(r->finfo.st_mode)) {
r->content_type = DIR_MAGIC_TYPE;
return OK;
}
if((i=ap_rind(fn,'.')) < 0) return DECLINED;
++i;
if ((type = ap_table_get (conf->encoding_types, &fn[i])))
{
r->content_encoding = type;
/* 返回上一個副檔名,嘗試將其用作類型 */
fn[i-1] = '\0';
if((i=ap_rind(fn,'.')) < 0) return OK;
++i;
}
if ((type = ap_table_get (conf->forced_types, &fn[i])))
{
r->content_type = type;
}
return OK;
}
伺服器模組配置背後的基本思想與目錄配置基本相同;都有一個建立函式和一個合併函式,當虛擬伺服器部分覆蓋了基本伺服器配置,並且必須計算組合結構時,將呼叫後者。(與目錄配置一樣,如果沒有指定合併函式,並且在某些虛擬伺服器中配置了模組,則預設情況下將忽略基本配置)。
唯一實質性的區別在於,當命令需要配置伺服器專用模組資料時,它需要轉到 cmd_parms
資料才能訪問它。下面是一個來自別名模組的示例,它還指示瞭如何返回語法錯誤(請注意,命令處理程式的目錄配置參數被宣告為虛擬參數,因為該模組實際上沒有目錄配置資料):
char *add_redirect(cmd_parms *cmd, void *dummy, char *f, char *url)
{
server_rec *s = cmd->server;
alias_server_conf *conf = (alias_server_conf *)
ap_get_module_config(s->module_config,&alias_module);
alias_entry *new = ap_push_array (conf->redirects);
if (!ap_is_url (url)) return "Redirect to non-URL";
new->fake = f; new->real = url;
return NULL;
}
可用語言: en