此處包含關於一般教學風格的部份備註。為求簡潔,此處中的所有結構宣告並不完整 --- 真正的宣告會包含更多時段,在此並未提及。這些宣告大部分保留給伺服器核心的一個或多個元件,模組應該審慎地進行變更。不過,在某些情況下,確實是我尚未完成的部份。歡迎使用這個最新技術。
最後,以下是綱要,提供給您後續內容的概略及順序
SetEnv
,並非完全適合用於其他地方。OK
來表示已完成處理。DECLINED
。在此情況下,伺服器的行為會與處理常式完全不存在時完全相同。*/*
(也就是說,一個萬用字元 MIME 種類規格)鍵值,來處理任何要求。然而,只有在伺服器已經嘗試過而且未能尋找一個更具體回應處理程式,來處理所要求物件的 MIME 種類時,萬用字元處理程式才會被呼叫(代表沒有該處理程式或它們全部拒絕)。處理程式本身是一個單一參數(也就是一個 request_rec
結構。詳見下文)的函數,它傳回一個整數,如上所述。
ScriptAlias
設定檔指令命令。它實際上比大部分模組還要複雜許多,但如果我們只有一個範例,它也可能是介入所有事務的那一個模組。讓我們從處理檔案開始。為了處理 CGI 指令碼,模組為它們宣告了一個回應用處理程式。由於 ScriptAlias
,它也有用來處理名稱轉換階段(辨認 ScriptAlias
的 URI)的處理程式,用來處理類型檢查階段(任何 ScriptAlias
的要求會被分類為一個 CGI 指令碼)。
模組需要維護一些關於(虛擬)伺服器的資訊,也就是生效的 ScriptAlias
;因此模組結構包含指向一個建構這些結構的函數的指標,以及指向另一個結合兩個結構(如果主伺服器和一個虛擬伺服器都有宣告的 ScriptAlias
)的指標。
最後,這個模組包含一個用來處理 ScriptAlias
命令本身的程式碼。這個特定的模組只宣告一個命令,但可能會還有更多,所以模組有宣告它們的命令的命令表格,並描述允許它們在哪裡以及它們將如何被呼叫。
關於某些命令參數的宣告類型之最終說明:pool
是指向資源群組結構的指標;伺服器用於追蹤已配置的記憶體、已開啟的檔案等等,可能是服務特定要求或處理其自身的組態程序中。這個方式下,當要求結束(或對組態群組來說,當伺服器重新啟動時),記憶體便可以一併釋放,檔案也可以一併關閉,而不需要任何人寫出明確的程式碼來追蹤並處理它們。此外,cmd_parms
結構包含關於正在讀取設定檔的各種資訊,以及其他有時對處理設定檔命令的功能(例如 ScriptAlias
)有用的狀態資訊。言歸正傳,模組本身
/* Declarations of handlers. */ int translate_scriptalias (request_rec *); int type_scriptalias (request_rec *); int cgi_handler (request_rec *); /* Subsdiary dispatch table for response-phase handlers, by MIME type */ handler_rec cgi_handlers[] = { { "application/x-httpd-cgi", cgi_handler }, { NULL } }; /* Declarations of routines to manipulate the module's configuration * info. Note that these are returned, and passed in, as void *'s; * the server core keeps track of them, but it doesn't, and can't, * know their internal structure. */ void *make_cgi_server_config (pool *); void *merge_cgi_server_config (pool *, void *, void *); /* Declarations of routines to handle config-file commands */ char *script_alias (cmd_parms *, void *per_dir_config, char *fake, char *real); command_rec cgi_cmds[] = { { "ScriptAlias", script_alias, NULL, RSRC_CONF, TAKE2, "a fakename and a realname"}, { NULL } }; module cgi_module = { STANDARD_MODULE_STUFF, NULL, /* initializer */ NULL, /* dir config creater */ NULL, /* dir merger --- default is to override */ 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 */ };
request_rec
結構。這個結構說明已對伺服器提出的特定要求,代表某個客戶端提出。在多數情況下,與客戶端每連線只會產生一個 request_rec
結構。
request_rec
的簡介request_rec
包含指向伺服器處理完要求後會清除的資源群組的指標;指向包含伺服器和連線資訊的結構,最重要的,還有要求本身的資訊。這些資訊中最重要的是一組簡短的字元串,說明要求物件的屬性,包括它的 URI、檔名、內容類型和內容編碼(分別由處理要求的翻譯和類型檢查處理函式填入)。
其他經常使用的資料項目是表格,提供客戶端原始要求的 MIME 標頭,MIME 標頭會在回應時回傳(模組可以任意加入),以及在服務要求過程中建立的所有子程序的環境變數。這些表格使用 table_get
和 table_set
常式來操作。
最後,有指向兩個資料結構的指標,而這些資料結構又指向各模組組態結構。特別是,這些指標指向模組為描述它已建構的方式來操作特定目錄(透過 .htaccess
檔案或 <Directory>
區段)而建立的資料結構,以及在服務要求過程中建立的私人資料(因此模組的處理函式在一個階段可以將「備忘錄」傳送給它們在其他階段的處理函式)。在 request_rec
指向的 server_rec
資料結構中,還有另一個此類組態向量,它包含每組(虛擬)伺服器組態資料。
以下是一個精簡的宣告,提供最常使用欄位
struct request_rec { pool *pool; conn_rec *connection; server_rec *server; /* What object is being requested */ 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 header environments, in and out. Also, an array containing * environment variables to be passed to subprocesses, so people can * write modules to add to that environment. * * The difference between headers_out and err_headers_out is that the * latter are printed even on error, and persist across internal redirects * (so the headers printed for ErrorDocument handlers will have them). */ table *headers_in; table *headers_out; table *err_headers_out; table *subprocess_env; /* Info about the request itself... */ 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. */ /* Info for logging */ char *the_request; int bytes_sent; /* A flag which modules can set, to indicate that the data being * returned is volatile, and clients should be told not to cache it. */ int no_cache; /* Various other config info which may change with .htaccess files * These are config vectors, with one void* pointer for each module * (the thing pointed to being the module's business). */ void *per_dir_config; /* Options set in config files, etc. */ void *request_config; /* Notes on *this* request */ };
request_rec
結構是透過從客戶端讀取 HTTP 要求並填入欄位來建立的。不過還是有些例外*.var
檔) 或 CGI 腳本提供的區域「位置」,那麼使用者請求的資源最終將位於與原本客戶端提供的 URI 不同的位置。在此情況下,伺服器會執行內部重新導向,為新的 URI 建立新的 request_rec
,並處理這些資料,而處理方式幾乎與客戶端直接請求新的 URI 相同。
ErrorDocument
在範圍內,則會發揮相同的內部重新導向機制。
這些處理常式可以使用函式 sub_req_lookup_file
和 sub_req_lookup_uri
,建立子請求;這會建立新的 request_rec
結構並進行處理,正如您所預期的那樣,直到要實際傳送回應為止 (但不包括這個點)。(如果子請求是針對與原始請求在相同目錄下的檔案,這些函式會跳過存取檢查)。
(伺服器端包含會透過函式 run_sub_request
,建立子請求並針對它們實際呼叫回應處理常式。)
request_rec
時,必須傳回一個 int
來表示發生了什麼事。這可以是REDIRECT
,則組件應將 Location
放入請求的 headers_out
,以表示應該將客戶端重新導向至何處。
request_rec
結構中的幾個欄位 (或者,對於存取檢查器而言,只需傳回正確的錯誤碼) 就可完成工作。然而,回應處理常式實際上必須發送請求回到客戶端。他們應透過函式 send_http_header
開始傳送 HTTP 回應標頭。(您不必採取任何特殊措施來略過為 HTTP/0.9 請求傳送標頭;函式會自行判斷不應執行任何動作)。如果請求標記為 header_only
,則這便是他們應該執行的所有動作;他們應該在之後傳回,而不嘗試任何進一步的輸出。
否則,他們應產生一個請求主體來適當回應客戶端。必要的基礎架構包含 rputc
和 rprintf
,用於內部產生的輸出,以及 send_fd
,用於將某些 FILE *
的內容直接複製到客戶端。
最後一個考量事項:執行對客戶端的 I/O 時,有進一步延遲的可能性。因此,在對客戶端發起 I/O 之前,事先設定逾時非常重要。
此時,您應該多多少少了解以下這段程式碼,它是處理沒有更具體處理常式的 GET
請求的處理常式。它也展示如何在特定回應處理常式中進行處理時,如何執行有條件的 GET
。(函式 pfopen
和 pfclose
將回傳到資源池機制中的 FILE *
,因此即使請求被中斷,它仍會被關閉)。
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 = set_content_length (r, r->finfo.st_size)) || (errstatus = set_last_modified (r, r->finfo.st_mtime))) return errstatus; f = pfopen (r->pool, r->filename, "r"); if (f == NULL) { log_reason("file permissions deny server access", r->filename, r); return FORBIDDEN; } register_timeout ("send", r); send_http_header (r); if (!r->header_only) { send_fd (f, r); } kill_timeout(r); pfclose (r->pool, f); return OK; }最後,如果上述內容全部超出能力範圍,則有幾種脫身方法。首先,如上所示,尚未產生任何輸出結果的回應處理常式,可以僅回傳錯誤碼,在這種情況下,伺服器將自動產生錯誤回應。其次,它可以透過呼叫
internal_redirect
轉移到其他處理常式,這是如上所述的內部重新導向機制呼叫方式。已執行內部重新導向的回應處理常式應始終回傳 OK
。(並非回應處理常式的處理常式呼叫 internal_redirect
將導致混淆)。
auth_type
、auth_name
和 requires
。get_basic_auth_pw
,它會自動設定 connection->user
結構欄位,以及 note_basic_auth_failure
,它會整理適當的 WWW-Authenticate:
標頭送回去)。request_rec
結構列表(這些結構透過 r->prev
和 r->next
指標執行),來處理此問題。在這種情況下,傳遞至記錄處理常式的 request_rec
結構,是最初為客戶端初始請求所建置的。請注意,只有鏈中的最後一個請求(即實際傳送回應的那個請求)才會正確顯示 bytes_sent 欄位。palloc
及類似函式destroy_sub_request
然而,只給予模組指令表格不足以將它們與伺服器核心完全分離。伺服器必須記住指令以便稍後對其採取動作。這涉及維護對模組私有的資料,這些資料可以是每個伺服器專屬的,或是每個目錄專屬的。大多數都是每個目錄專屬的,特別包括存取控制和授權資訊,以及如何從字尾決定檔案類型的資訊,這些資訊會受到 AddType
和 DefaultType
指令變更,以此類推。普遍的規範哲學是,可透過目錄組態的所有內容都應這麼做;每個伺服器資訊通常在資訊標準模組集中使用,例如 Alias
和 Redirect
等在請求連結到基礎檔案系統特定位置前就開始運作的資訊。
模擬 NCSA 伺服器的另一個需求在於能夠處理每個目錄的組態檔,通常稱為 .htaccess
檔,儘管這些檔案即使在 NCSA 伺服器裡也可能包含與存取控制無關的指令。於是,在 URI -> 檔名轉換之後但在執行任何其他階段之前,伺服器會沿著基礎檔案系統的目錄階層向下移動,追蹤已轉換路徑名稱,以讀取可能存在的任何 .htaccess
檔案。接著必須將讀取進來的資訊與伺服器本身設定檔的適用資訊進行合併(不是來自 access.conf
中的 <Directory>
區段,就是來自 srm.conf
中的預設值,而後者幾乎在所有方面都近乎完全像 <Directory />
一樣運作)。
最後,在提供涉及讀取 .htaccess
檔案的請求後,我們需要清除為處理它們而配置的儲存。其解決方式與其他有類似問題的地方相同,將這些結構連結到每個交易資源池即可。
mod_mime.c
中發揮作用的,這個檔案定義了檔案類型處理函式,它模擬了 NCSA 伺服器根據字尾來判斷檔案類型的行為。我們在此要探討的,就是實作 AddType
和 AddEncoding
指令的程式碼。這些指令會出現在 .htaccess
檔案中,所以它們必須在模組的私有 per-directory 資料中處理,它實際上包含兩個用於 MIME 類型和編碼資訊的獨立 table
,並宣告如下typedef struct { table *forced_types; /* Additional AddTyped stuff */ table *encoding_types; /* Added with AddEncoding... */ } mime_dir_config;當伺服器讀取包含 MIME 模組指令之一的組態檔案或
<Directory>
區塊時,它需要建立一個 mime_dir_config
結構,讓這些指令能夠操作。它透過執行模組「製造 per-dir 配置槽」中找到的函式來達成這件事,帶兩個參數:要套用這個組態資訊的目錄名稱(或 srm.conf
的 NULL
),以及應執行配置的資源池指標。(如果我們讀取 .htaccess
檔案,那資源池就是該請求的 per-request 資源池;否則,它就是用於組態資料的資源池,並在重新啟動時清除。在任一種情況下,建立的結構在清除資源池時消失,非常重要,必要時必須在資源池中註冊清除機制)。
對於 MIME 模組,per-dir 配置建立函式只會將上述結構 palloc
,並建立幾個 table
來填滿它。它看起來像這個樣子
void *create_mime_dir_config (pool *p, char *dummy) { mime_dir_config *new = (mime_dir_config *) palloc (p, sizeof(mime_dir_config)); new->forced_types = make_table (p, 4); new->encoding_types = make_table (p, 4); return new; }現在,假設我們剛讀取一個
.htaccess
檔案。我們已經有了階層中下一個目錄的 per-directory 組態結構。如果我們剛讀取的 .htaccess
檔案沒有任何 AddType
或 AddEncoding
指令,則它對 MIME 模組的 per-directory 配置結構仍然有效,我們可以使用它。否則,我們需要以某種方式合併兩個結構。為此,伺服器會呼叫模組的 per-directory 配置合併函式(如果存在)。該函式採用三個參數:要合併的兩個結構和在其中配置結果的資源池。對於 MIME 模組,所有需要做的就是將新的 per-directory 配置結構中的表格疊加在父結構的表格上
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 *)palloc (p, sizeof(mime_dir_config)); new->forced_types = overlay_tables (p, subdir->forced_types, parent_dir->forced_types); new->encoding_types = overlay_tables (p, subdir->encoding_types, parent_dir->encoding_types); return new; }注意事項 --- 如果沒有 per-directory 合併函式,伺服器只會使用子目錄的組態資訊,而忽略父目錄的組態資訊。對於某些模組,這樣做沒問題(例如,包含模組,其 per-directory 組態資訊僅包含
XBITHACK
的狀態),對於這些模組,您只需不宣告一個,並將模組中對應的結構槽留為 NULL
。
AddType
和 AddEncoding
命令。為命令尋找,伺服器會在模組的 command table
中尋找。該表包含有關命令需要多少個引數、以及用什麼格式、在哪裡允許等等的資訊。那些資訊足夠讓伺服器使用已準備好的引數呼叫大多數命令處理功能。不多介紹,讓我們看看 AddType
命令處理常式,如下所示(AddEncoding
命令看起來基本上相同,這裡不會示範)char *add_type(cmd_parms *cmd, mime_dir_config *m, char *ct, char *ext) { if (*ext == '.') ++ext; 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, "a mime type followed by a file extension" }, { "AddEncoding", add_encoding, NULL, OR_FILEINFO, TAKE2, "an encoding (e.g., gzip), followed by a file extension" }, { NULL } };這些表中的項目是
cmd_parms
結構中傳遞給命令處理常式的 (void *)
指標 --- 如果有許多類似命令由同一個功能處理,這會很有用。AllowOverride
選項都有對應的遮罩,還有一個額外的位元遮罩 RSRC_CONF
,指示命令可能出現在伺服器的設定檔中,但 並未 出現在任何 .htaccess
檔中。TAKE2
表示兩個先準備分析的引數。其他選項為 TAKE1
,表示一個先準備分析的引數,FLAG
,表示引數應該為 On
或 Off
,並傳遞為布林旗標,RAW_ARGS
,導致伺服器給予命令未經分析的原始引數(除了命令名稱本身)。另外還有 ITERATE
,表示處理程式看起來和 TAKE1
一樣,但如果有多個引數存在,應呼叫多次,最後為 ITERATE2
,表示命令處理程式看起來像 TAKE2
,但如果有多個引數存在,則應呼叫多次,把第一個引數設定為常數。NULL
)。get_module_config
函數從 request_rec
的每個目錄組態向量中提取。int find_ct(request_rec *r) { int i; char *fn = pstrdup (r->pool, r->filename); mime_dir_config *conf = (mime_dir_config *)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=rind(fn,'.')) < 0) return DECLINED; ++i; if ((type = table_get (conf->encoding_types, &fn[i]))) { r->content_encoding = type; /* go back to previous extension to try to use it as a type */ fn[i-1] = '\0'; if((i=rind(fn,'.')) < 0) return OK; ++i; } if ((type = 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 *)get_module_config(s->module_config,&alias_module); alias_entry *new = push_array (conf->redirects); if (!is_url (url)) return "Redirect to non-URL"; new->fake = f; new->real = url; return NULL; }