Apache HTTP Server 2.4 版
本文將探討如何為 Apache HTTP Server 2.4 建立模組,並以 mod_example
範例模組說明。本文件的第一部分,此模組的目的是計算並列印您 Web 伺服器上現有檔案的各種摘要值,只要我們存取 URL http://hostname/filename.sum
。例如,如果我們想要知道位於 http://www.example.com/index.html
的檔案的 MD5 摘要值,我們將拜訪 http://www.example.com/index.html.sum
。
本文件第二部分處理組態指令和內容感知能力,我們將檢視一個模組,它會將自己的組態寫出到用戶端。
首先,您應具備如何使用 C 程式語言的基本知識。在大部分情況下,我們將盡可能地教學,並提供連結說明範例中使用的函式,但有時也必須假設「它有用」或者親自深入探索各種函式呼叫的如何和為何。
最後,您必須基本了解如何在 Apache HTTP Server 中載入並組態模組,以及如何取得 Apache 標頭(如果您尚未取得),因為編譯新模組需要這些標頭。
要編譯此文件建立的原始碼,我們將使用 APXS 。假設您的原始檔稱為 mod_example.c,編譯、安裝和啟動模組和這個一樣簡單
apxs -i -a -c mod_example.c
每個模組都從同一個宣告開始,或如果您愿意的話,名稱標籤會將模組定義為Apache 中的單獨實體
module AP_MODULE_DECLARE_DATA example_module = { STANDARD20_MODULE_STUFF, create_dir_conf, /* Per-directory configuration handler */ merge_dir_conf, /* Merge handler for per-directory configurations */ create_svr_conf, /* Per-server configuration handler */ merge_svr_conf, /* Merge handler for per-server configurations */ directives, /* Any directives we may have for httpd */ register_hooks /* Our hook registering function */ };
這段程式碼讓伺服器知道我們現在已註冊系統中的新模組,且其名稱為 example_module
。模組名稱主要用於兩件事
目前,我們只關注模組名稱的第一個目的,當我們需要載入模組時會開始運作
LoadModule example_module modules/mod_example.so
基本上,這告訴伺服器開啟 mod_example.so
並尋找名為 example_module
的模組。
名稱標籤中也有一堆參考我們希望如何處理各種事情:我們在組態檔或 .htaccess 中回應哪些指令,我們如何在特定內容中操作,以及我們有興趣註冊哪些處理常式至 Apache HTTP 服務。我們將會在後面的文件中回歸所有這些元素。
在 Apache HTTP Server 2.4 中處理請求時,您首先需要對請求處理流程建立掛勾。掛勾基本上是給伺服器的訊息,告訴伺服器您願意處理或至少檢視客戶端提供的特定請求。所有處理常式,不管是 mod_rewrite、mod_authn_ *、mod_proxy 等,都掛接至請求流程的特定部分。您可能知道,模組會服務於不同目的;有些是驗證/授權處理常式,有些是檔案或指令碼處理常式,而有些第三模組會改寫 URI 或代理程式內容。此外,最後還是取決於伺服器的使用者如何以及何時套用每個模組。因此,伺服器本身不會假設哪個模組負責處理特定請求,並且會詢問每個模組是否對特定請求感興趣。然後再交由每個模組溫和地拒絕處理請求、接受處理請求或徹底拒絕請求被處理,如同驗證/授權模組所做的那樣
為了讓類似 mod_example 的處理程式更容易得知伺服器是否應處理客戶端請求的內容,伺服器會提供指示,提示模組是否需要協助。其中兩個指示為 AddHandler
和 SetHandler
。我們來看看使用 AddHandler
的範例。在我們的範例情況中,我們希望由 mod_example
處理所有以 .sum 結尾的請求,因此我們會新增一個組態指令,指示伺服器執行此動作
AddHandler example-handler .sum
伺服器得知的訊息如下:每當我們收到 URI 結尾為 .sum 的請求,我們就會讓所有模組知道我們正在尋找名為「example-handler」的名稱。因此,當伺服器處理以 .sum 結尾的請求時,伺服器會讓所有模組知道此請求應該由「example-handler」處理。稍後在我們開始建置 mod_example 時,我們會檢查由 AddHandler
轉傳的這個處理標記,並根據這個標記的值回覆伺服器。
首先,我們僅想要建立一個簡單的處理程式,當請求特定 URL 時,由處理程式回覆客戶端瀏覽器,因此我們現在暫時不設定組態處理程式和指示。我們的初始模組定義會如下所示
module AP_MODULE_DECLARE_DATA example_module = { STANDARD20_MODULE_STUFF, NULL, NULL, NULL, NULL, NULL, register_hooks /* Our hook registering function */ };
這讓伺服器得知我們沒有興趣執行任何花俏的事情,我們只想要掛接到請求,並且可能會處理其中一些請求。
在範例宣告中引用的 register_hooks
這個函式的名稱,是我們用來管理如何掛接到請求程序的函式。在此範例模組中,函式只有一個目的:建立一個簡單的掛鉤,在處理完所有重寫、存取控制等之後,觸發這個掛鉤。因此,我們會讓伺服器知道,我們想要作為最後的模組之一,掛接到伺服器的程序
static void register_hooks(apr_pool_t *pool) { /* Create a hook in the request handler, so we get called when a request arrives */ ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST); }
example_handler
參照是會處理請求的函式。我們將在下一章討論如何建立處理程式。
掛接到請求處理階段只是你可以建立的眾多掛鉤之一。其他一些掛鉤方式如下
ap_hook__child_init
:放置一個在執行子程序時執行的掛鉤(通常用於在伺服器分派後初始化模組)ap_hook_pre_config
:放置一個在讀取任何組態資料前執行的掛鉤(極早期的掛鉤)ap_hook_post_config
:放置一個在解析組態後,但在伺服器分派之前執行的掛鉤ap_hook_pre_translate_name
:在 URI 需要轉換為伺服器上的檔案名稱之前,放置一個在執行時會執行的掛鉤,執行前會解碼ap_hook_translate_name
:放置一個在 URI 需要轉換為伺服器上的檔案名稱時會執行的掛鉤(想想看 mod_rewrite
)ap_hook_quick_handler
:類似於 ap_hook_handler
,只是它在任何其他要求掛鉤(轉換、驗證、修正等)之前執行ap_hook_log_transaction
:放置一個在伺服器即將新增當前要求的記錄日誌時會執行的掛鉤處理常式基本上是一個當伺服器接收到要求時會接收回呼的函式。傳遞當前要求的記錄(它是如何建立的、傳遞哪些標頭和要求、誰提出要求等),負責告訴伺服器它對要求沒興趣或使用提供的工具處理要求。
讓我們從建立一個很簡單的要求處理常式開始,它執行以下動作
text/html
在 C 程式碼中,我們的範例處理常式現在看起來像這樣
static int example_handler(request_rec *r) { /* First off, we need to check if this is a call for the "example-handler" handler. * If it is, we accept it and do our things, if not, we simply return DECLINED, * and the server will try somewhere else. */ if (!r->handler || strcmp(r->handler, "example-handler")) return (DECLINED); /* Now that we are handling this request, we'll write out "Hello, world!" to the client. * To do so, we must first set the appropriate content type, followed by our output. */ ap_set_content_type(r, "text/html"); ap_rprintf(r, "Hello, world!"); /* Lastly, we must tell the server that we took care of this request and everything went fine. * We do so by simply returning the value OK to the server. */ return OK; }
現在,我們把學到的一切整合在一起,並得出一個看起來像 mod_example_1.c 的程式。此範例中使用的函式將在後面的章節 "你應該要知道的一些有用函式" 中說明。
任何要求最重要的部分就是要求記錄 。在呼叫處理常式函式時,這是由在每次呼叫中傳遞的 request_rec*
結構表示的。這個結構通常在模組中稱為 r
,包含了模組處理所有 HTTP 要求並做出適當回應所需的所有資訊。
request_rec
結構的幾個主要元素如下
r->handler (char*):
包含伺服器當前要求做的處理常式名稱r->method (char*):
包含所使用的 HTTP 方法,例如 GET 或 POSTr->filename (char*):
包含經過轉換的檔案名稱,由客戶端要求r->args (char*):
包含要求的查詢字串(如果有)r->headers_in (apr_table_t*):
包含客戶端傳送的所有標頭r->connection (conn_rec*):
包含當前連線相關資訊的記錄r->user (char*):
如果 URI 需要驗證,則設為提供的使用者名稱r->useragent_ip (char*):
連線至我們的客戶端的 IP 位址r->pool (apr_pool_t*)
:這項要求的記憶體池。我們將在「記憶體管理」單元中討論這個議題。request_rec
結構中所含所有值的完整清單可在標頭檔 httpd.h
中或在 http://ci.apache.org/projects/httpd/trunk/doxygen/structrequest__rec.html 中找到。
讓我們在另一個範例處理程式中嘗試一些這些變數
static int example_handler(request_rec *r) { /* Set the appropriate content type */ ap_set_content_type(r, "text/html"); /* Print out the IP address of the client connecting to us: */ ap_rprintf(r, "<h2>Hello, %s!</h2>", r->useragent_ip); /* If we were reached through a GET or a POST request, be happy, else sad. */ if ( !strcmp(r->method, "POST") || !strcmp(r->method, "GET") ) { ap_rputs("You used a GET or a POST method, that makes us happy!<br/>", r); } else { ap_rputs("You did not use POST or GET, that makes us sad :(<br/>", r); } /* Lastly, if there was a query string, let's print that too! */ if (r->args) { ap_rprintf(r, "Your query string was: %s", r->args); } return OK; }
Apache 倚賴於處理程式的回傳值來表示某個要求是否已處理,如果已處理,則表示要求是否順利進行。如果模組對處理特定要求沒有興趣,它應該總是回傳值 DECLINED
。如果它正在處理要求,它應該回傳一般值 OK
,或具體 HTTP 狀態碼,例如
static int example_handler(request_rec *r) { /* Return 404: Not found */ return HTTP_NOT_FOUND; }
回傳 OK
或 HTTP 狀態碼不必然表示要求將結束。伺服器可能仍有其他處理程式對這個要求有興趣,例如記錄模組,在要求成功後,它會寫下所要求內容的摘要以及進行過程。若要在您的模組完成後完整停止並防止任何進一步的處理,您可以回傳值 DONE
,讓伺服器知道它應該停止對此要求的所有活動,並繼續執行下一個要求,而不通知其他處理程式。
一般回應碼
DECLINED
:我們不處理這項要求OK
:我們處理了這項要求,而且一切順利DONE
:我們處理了這項要求,伺服器應該直接關閉這個執行緒,不做進一步的處理HTTP 專用回傳碼(摘錄)
HTTP_OK (200)
:要求順利HTTP_MOVED_PERMANENTLY (301)
:資源已搬移到新網址HTTP_UNAUTHORIZED (401)
:客戶端無權造訪此頁面HTTP_FORBIDDEN (403)
:拒絕存取HTTP_NOT_FOUND (404)
:檔案不存在HTTP_INTERNAL_SERVER_ERROR (500)
:內部伺服器錯誤(不言自明)ap_rputs(const char *string, request_rec *r)
: ap_rputs("Hello, world!", r);
ap_rprintf
: printf
,只不過它將結果傳送至客戶端。ap_rprintf(r, "Hello, %s!", r->useragent_ip);
ap_set_content_type(request_rec *r, const char *type)
: ap_set_content_type(r, "text/plain"); /* force a raw text output */
在 Apache HTTP 伺服器 2.4 中,由於記憶體池系統,管理資源相當容易。基本上,每個伺服器、連線和要求,都有自己的記憶體池,這些記憶體池會在作用範圍結束,例如要求完成時或伺服器處理關閉時,進行清理。模組只需要連結到此記憶體池,就不需要擔心後續的清理作業──非常棒,對吧?
我們的模組將主要為每個要求配置記憶體,一建立新的物件就使用 r->pool
參考是適宜的作法。在池中配置記憶體的函式包括:
void* apr_palloc( apr_pool_t *p, apr_size_t size)
:在池中為你配置大小 size
的位元組數void* apr_pcalloc( apr_pool_t *p, apr_size_t size)
:在池中為你配置大小 size
的位元組數,並將所有位元組設定為 0char* apr_pstrdup( apr_pool_t *p, const char *s)
:建立字串 s
的複本。這在複製常數值以供編輯時很有用char* apr_psprintf( apr_pool_t *p, const char *fmt, ...)
:類似於 sprintf
,除了由伺服器提供一個適當配置的目標變數將這些函式寫入範例處理函式中
static int example_handler(request_rec *r) { const char *original = "You can't edit this!"; char *copy; int *integers; /* Allocate space for 10 integer values and set them all to zero. */ integers = apr_pcalloc(r->pool, sizeof(int)*10); /* Create a copy of the 'original' variable that we can edit. */ copy = apr_pstrdup(r->pool, original); return OK; }
我們的模組的上述內容非常完善,不需要任何預先初始化的變數或結構。但是,如果我們想要在要求開始湧入之前,於早期初始化某些內容,只要在我們的 register_hooks
函式中加入呼叫一個函式的指令即可解決
static void register_hooks(apr_pool_t *pool) { /* Call a function that initializes some stuff */ example_init_function(pool); /* Create a hook in the request handler, so we get called when a request arrives */ ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST); }
在此預先要求初始化函式中,我們不會使用與為基於要求的函式配置資源時相同的池。而是會使用伺服器提供給我們,用於在每個程序基礎層級配置記憶體的池。
在我們的範例模組中,我們要把功能加入,以便檢查客戶端要查看哪種類型的雜湊值(MD5 或 SHA1)。這可以透過將查詢字串加入要求中來解決。查詢字串通常由數個金鑰和值組成,組合起來成為一個字串,例如 valueA=yes&valueB=no&valueC=maybe
。剖析這些內容並取得所需的資料是模組的任務。在我們的範例中,我們將尋找稱為 digest
的金鑰,如果設定為 md5
,則會產生 MD5 雜湊值,否則會產生 SHA1 雜湊值。
從 Apache HTTP 伺服器 2.4 推出以來,來自 GET 和 POST 要求的剖析要求資料已變得前所未有地容易。我們所需要的只有四行,就能剖析 GET 和 POST 資料
apr_table_t *GET; apr_array_header_t*POST; ap_args_to_table(r, &GET); ap_parse_form_data(r, NULL, &POST, -1, 8192);
在我們的特定範例模組中,我們正在尋找來自 query 字串的 digest
值,該值現在駐留在稱為 GET
的資料表中。若要擷取此值,我們只需要執行簡單的作業
/* Get the "digest" key from the query string, if any. */ const char *digestType = apr_table_get(GET, "digest"); /* If no key was returned, we will set a default value instead. */ if (!digestType) digestType = "sha1";
POST 和 GET 資料所使用的結構並非完全相同,所以如果我們要從 POST 資料中擷取值而不是來自 query 字串,我們必須訴求一些額外的指令行,如本文件最後一章中 此範例 中所述。
現在我們已經學會如何剖析表單資料和管理我們的資源,我們可以繼續建立進階版的模組,以區分檔案的 MD5 或 SHA1 摘要
static int example_handler(request_rec *r) { int rc, exists; apr_finfo_t finfo; apr_file_t *file; char *filename; char buffer[256]; apr_size_t readBytes; int n; apr_table_t *GET; apr_array_header_t *POST; const char *digestType; /* Check that the "example-handler" handler is being called. */ if (!r->handler || strcmp(r->handler, "example-handler")) return (DECLINED); /* Figure out which file is being requested by removing the .sum from it */ filename = apr_pstrdup(r->pool, r->filename); filename[strlen(filename)-4] = 0; /* Cut off the last 4 characters. */ /* Figure out if the file we request a sum on exists and isn't a directory */ rc = apr_stat(&finfo, filename, APR_FINFO_MIN, r->pool); if (rc == APR_SUCCESS) { exists = ( (finfo.filetype != APR_NOFILE) && !(finfo.filetype & APR_DIR) ); if (!exists) return HTTP_NOT_FOUND; /* Return a 404 if not found. */ } /* If apr_stat failed, we're probably not allowed to check this file. */ else return HTTP_FORBIDDEN; /* Parse the GET and, optionally, the POST data sent to us */ ap_args_to_table(r, &GET); ap_parse_form_data(r, NULL, &POST, -1, 8192); /* Set the appropriate content type */ ap_set_content_type(r, "text/html"); /* Print a title and some general information */ ap_rprintf(r, "<h2>Information on %s:</h2>", filename); ap_rprintf(r, "<b>Size:</b> %u bytes<br/>", finfo.size); /* Get the digest type the client wants to see */ digestType = apr_table_get(GET, "digest"); if (!digestType) digestType = "MD5"; rc = apr_file_open(&file, filename, APR_READ, APR_OS_DEFAULT, r->pool); if (rc == APR_SUCCESS) { /* Are we trying to calculate the MD5 or the SHA1 digest? */ if (!strcasecmp(digestType, "md5")) { /* Calculate the MD5 sum of the file */ union { char chr[16]; uint32_t num[4]; } digest; apr_md5_ctx_t md5; apr_md5_init(&md5); readBytes = 256; while ( apr_file_read(file, buffer, &readBytes) == APR_SUCCESS ) { apr_md5_update(&md5, buffer, readBytes); } apr_md5_final(digest.chr, &md5); /* Print out the MD5 digest */ ap_rputs("<b>MD5: </b><code>", r); for (n = 0; n < APR_MD5_DIGESTSIZE/4; n++) { ap_rprintf(r, "%08x", digest.num[n]); } ap_rputs("</code>", r); /* Print a link to the SHA1 version */ ap_rputs("<br/><a href='?digest=sha1'>View the SHA1 hash instead</a>", r); } else { /* Calculate the SHA1 sum of the file */ union { char chr[20]; uint32_t num[5]; } digest; apr_sha1_ctx_t sha1; apr_sha1_init(&sha1); readBytes = 256; while ( apr_file_read(file, buffer, &readBytes) == APR_SUCCESS ) { apr_sha1_update(&sha1, buffer, readBytes); } apr_sha1_final(digest.chr, &sha1); /* Print out the SHA1 digest */ ap_rputs("<b>SHA1: </b><code>", r); for (n = 0; n < APR_SHA1_DIGESTSIZE/4; n++) { ap_rprintf(r, "%08x", digest.num[n]); } ap_rputs("</code>", r); /* Print a link to the MD5 version */ ap_rputs("<br/><a href='?digest=md5'>View the MD5 hash instead</a>", r); } apr_file_close(file); } /* Let the server know that we responded to this request. */ return OK; }
完整的這版本可以在此處找到:mod_example_2.c。
在本文文件的下一個部份中,我們將把焦點從摘要模組轉移開,並建立一個新的範例模組,其唯一的目的是寫出其自己的組態。目的在於檢視伺服器如何使用組態,以及當你開始為模組撰寫進階組態時會發生什麼事。
如果你正在閱讀本文件,那麼你可能已經知道什麼是組態指令。簡而言之,指令是一種告知個別模組(或一群模組)如何運作的方式,例如這些指令控制 mod_rewrite
的運作方式
RewriteEngine On RewriteCond "%{REQUEST_URI}" "^/foo/bar" RewriteRule "^/foo/bar/(.*)$" "/foobar?page=$1"
每個這些組態指令都由一個獨立的函數處理,該函數會剖析給定的參數並建立相應的組態。
首先,我們將在 C 空間中建立一個基本的組態
typedef struct { int enabled; /* Enable or disable our module */ const char *path; /* Some path to...something */ int typeOfAction; /* 1 means action A, 2 means action B and so on */ } example_config;
現在,讓我們透過建立一個非常小的模組(僅列印硬式編碼組態)來具體說明。你會注意到我們使用 register_hooks
函數將組態值初始化為其預設值
typedef struct { int enabled; /* Enable or disable our module */ const char *path; /* Some path to...something */ int typeOfAction; /* 1 means action A, 2 means action B and so on */ } example_config; static example_config config; static int example_handler(request_rec *r) { if (!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED); ap_set_content_type(r, "text/plain"); ap_rprintf(r, "Enabled: %u\n", config.enabled); ap_rprintf(r, "Path: %s\n", config.path); ap_rprintf(r, "TypeOfAction: %x\n", config.typeOfAction); return OK; } static void register_hooks(apr_pool_t *pool) { config.enabled = 1; config.path = "/foo/bar"; config.typeOfAction = 0x00; ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST); } /* Define our module as an entity and assign a function for registering hooks */ module AP_MODULE_DECLARE_DATA example_module = { STANDARD20_MODULE_STUFF, NULL, /* Per-directory configuration handler */ NULL, /* Merge handler for per-directory configurations */ NULL, /* Per-server configuration handler */ NULL, /* Merge handler for per-server configurations */ NULL, /* Any directives we may have for httpd */ register_hooks /* Our hook registering function */ };
到目前為止都很好。若要存取我們的新處理常式,我們可以將下列內容加入我們的組態
<Location "/example"> SetHandler example-handler </Location>
當我們拜訪時,我們會看到我們的模組列印出我們的目前組態。
如果我們想要變更我們的組態,不是透過將新值硬式編碼到模組中,而是透過使用 httpd.conf 檔案或可能是 .htaccess 檔案怎麼辦?是時候讓伺服器知道我們希望這樣做。為此,我們必須先變更我們的 名稱標籤 ,以包含我們想要向伺服器註冊的組態指令
module AP_MODULE_DECLARE_DATA example_module = { STANDARD20_MODULE_STUFF, NULL, /* Per-directory configuration handler */ NULL, /* Merge handler for per-directory configurations */ NULL, /* Per-server configuration handler */ NULL, /* Merge handler for per-server configurations */ example_directives, /* Any directives we may have for httpd */ register_hooks /* Our hook registering function */ };
這會告訴伺服器我們現在接受來自組態檔案的指令,而且稱為 example_directives
的結構包含我們指令和運作方式的資訊。由於我們的模組組態中有三個不同的變數,故我們將加入一個結構,其中包含三個指令和結尾的 NULL
static const command_rec example_directives[] = { AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, RSRC_CONF, "Enable or disable mod_example"), AP_INIT_TAKE1("examplePath", example_set_path, NULL, RSRC_CONF, "The path to whatever"), AP_INIT_TAKE2("exampleAction", example_set_action, NULL, RSRC_CONF, "Special action value!"), { NULL } };
正如你所見,每個指令都需要設定至少 5 個參數
AP_INIT_TAKE1
:這是一個告訴伺服器此指令需要一個且僅有一個參數的巨集。如果我們需要兩個參數,我們可以使用巨集 AP_INIT_TAKE2
等(更多巨集請參閱 httpd_conf.h)。exampleEnabled
:這是指令的名稱。更精確來說,這是使用者必須在設定檔中指定的內容,才能在模組中呼叫組態變更。example_set_enabled
:這是解析指令並適當地設定組態的 C 函式參考。我們將在下一段討論製作此函式的步驟。RSRC_CONF
:告訴伺服器可以在哪裡使用此指令。我們將在後面的章節詳細說明此值,但目前而言,RSRC_CONF
表示伺服器將只在伺服器情境中接受這些指令。"啟用或停用...."
:這只是簡要說明指令的功能。(定義中「遺失」的參數通常設定為 NULL
,但這是個選用函式。在執行解析參數的初始函式之後,可以執行這個函式。通常會省略它,因為用於驗證參數的函式可以用於設定參數。)
在告訴伺服器為我們的模組預期一些指令後,現在是製作一些用於處理這些指令的函式的時候了。指令會在組態檔案中讀取純文字,因此它會傳遞給指令處理函式的內容是一個或多個字串,我們必須辨識並執行這些字串。你會注意到,由於我們設定 exampleAction
指令為接受兩個參數,因此其 C 函式還有一個額外定義的參數
/* Handler for the "exampleEnabled" directive */ const char *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg) { if(!strcasecmp(arg, "on")) config.enabled = 1; else config.enabled = 0; return NULL; } /* Handler for the "examplePath" directive */ const char *example_set_path(cmd_parms *cmd, void *cfg, const char *arg) { config.path = arg; return NULL; } /* Handler for the "exampleAction" directive */ /* Let's pretend this one takes one argument (file or db), and a second (deny or allow), */ /* and we store it in a bit-wise manner. */ const char *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char *arg2) { if(!strcasecmp(arg1, "file")) config.typeOfAction = 0x01; else config.typeOfAction = 0x02; if(!strcasecmp(arg2, "deny")) config.typeOfAction += 0x10; else config.typeOfAction += 0x20; return NULL; }
在設定我們的指令和為它們設定處理函式後,現在我們可以將組件編譯到一個大型檔案中
/* mod_example_config_simple.c: */ #include <stdio.h> #include "apr_hash.h" #include "ap_config.h" #include "ap_provider.h" #include "httpd.h" #include "http_core.h" #include "http_config.h" #include "http_log.h" #include "http_protocol.h" #include "http_request.h" /* ============================================================================== Our configuration prototype and declaration: ============================================================================== */ typedef struct { int enabled; /* Enable or disable our module */ const char *path; /* Some path to...something */ int typeOfAction; /* 1 means action A, 2 means action B and so on */ } example_config; static example_config config; /* ============================================================================== Our directive handlers: ============================================================================== */ /* Handler for the "exampleEnabled" directive */ const char *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg) { if(!strcasecmp(arg, "on")) config.enabled = 1; else config.enabled = 0; return NULL; } /* Handler for the "examplePath" directive */ const char *example_set_path(cmd_parms *cmd, void *cfg, const char *arg) { config.path = arg; return NULL; } /* Handler for the "exampleAction" directive */ /* Let's pretend this one takes one argument (file or db), and a second (deny or allow), */ /* and we store it in a bit-wise manner. */ const char *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char *arg2) { if(!strcasecmp(arg1, "file")) config.typeOfAction = 0x01; else config.typeOfAction = 0x02; if(!strcasecmp(arg2, "deny")) config.typeOfAction += 0x10; else config.typeOfAction += 0x20; return NULL; } /* ============================================================================== The directive structure for our name tag: ============================================================================== */ static const command_rec example_directives[] = { AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, RSRC_CONF, "Enable or disable mod_example"), AP_INIT_TAKE1("examplePath", example_set_path, NULL, RSRC_CONF, "The path to whatever"), AP_INIT_TAKE2("exampleAction", example_set_action, NULL, RSRC_CONF, "Special action value!"), { NULL } }; /* ============================================================================== Our module handler: ============================================================================== */ static int example_handler(request_rec *r) { if(!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED); ap_set_content_type(r, "text/plain"); ap_rprintf(r, "Enabled: %u\n", config.enabled); ap_rprintf(r, "Path: %s\n", config.path); ap_rprintf(r, "TypeOfAction: %x\n", config.typeOfAction); return OK; } /* ============================================================================== The hook registration function (also initializes the default config values): ============================================================================== */ static void register_hooks(apr_pool_t *pool) { config.enabled = 1; config.path = "/foo/bar"; config.typeOfAction = 3; ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST); } /* ============================================================================== Our module name tag: ============================================================================== */ module AP_MODULE_DECLARE_DATA example_module = { STANDARD20_MODULE_STUFF, NULL, /* Per-directory configuration handler */ NULL, /* Merge handler for per-directory configurations */ NULL, /* Per-server configuration handler */ NULL, /* Merge handler for per-server configurations */ example_directives, /* Any directives we may have for httpd */ register_hooks /* Our hook registering function */ };
現在,我們可以在 httpd.conf 檔案中變更硬式編碼組態,方法是新增幾行
ExampleEnabled On ExamplePath "/usr/bin/foo" ExampleAction file allow
因此,我們套用組態、瀏覽我們網站上的 /example
,我們就會發現組態已經調整為我們在組態檔案中撰寫的內容。
在 Apache HTTP 伺服器 2.4 中,不同的 URL、虛擬主機、目錄等對伺服器的使用者而言可能具有不同的意義,而不同的脈絡會讓模組必須運作的方式不同。例如,我們假設你為 mod_rewrite 設定了這個組態
<Directory "/var/www"> RewriteCond "%{HTTP_HOST}" "^example.com$" RewriteRule "(.*)" "http://www.example.com/$1" </Directory> <Directory "/var/www/sub"> RewriteRule "^foobar$" "index.php?foobar=true" </Directory>
在此範例中,你為 mod_rewrite 設定兩個不同的脈絡
/var/www
中,所有 http://example.com
的要求都必須轉到 http://www.example.com
/var/www/sub
中,所有 foobar
的要求都必須轉到 index.php?foobar=true
如果 mod_rewrite (或該伺服器本身) 沒有情境感知,那麼這些改寫規則會套用至所有發出的請求,不論請求來自哪裡,以何種方式發出。不過,由於模組可以從伺服器直接取得特定於情境的設定,因此模組不需要自己知道哪些指令在該情境中有效,因為伺服器會負責處理。
模組如何取得問題中的伺服器、目錄或位置的特定設定?透過以下簡單的呼叫來執行
example_config *config = (example_config*) ap_get_module_config(r->per_dir_config, &example_module);
就是這樣!當然,幕後會發生很多事情,我們會在本章節中討論,首先是伺服器如何得知我們的設定樣貌,以及如何設定為特定情境中的樣貌。
在本章節中,我們將使用稍微改良的先前情境結構版本。我們會設定一個 `context` 變數,可以在伺服器上使用它來追蹤哪些情境設定在各個地方被使用
typedef struct { char context[256]; char path[256]; int typeOfAction; int enabled; } example_config;
我們的請求處理常式也會經過修改,但仍然很簡單
static int example_handler(request_rec *r) { if(!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED); example_config *config = (example_config*) ap_get_module_config(r->per_dir_config, &example_module); ap_set_content_type(r, "text/plain"); ap_rprintf("Enabled: %u\n", config->enabled); ap_rprintf("Path: %s\n", config->path); ap_rprintf("TypeOfAction: %x\n", config->typeOfAction); ap_rprintf("Context: %s\n", config->context); return OK; }
在我們開始讓模組具有情境感知之前,我們必須先定義我們要接受哪些情境。如同我們在先前章節中所見,定義一個指令需要設定五個元素
AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, RSRC_CONF, "Enable or disable mod_example"),
`RSRC_CONF` 定義告訴伺服器我們只會在全球伺服器情境中允許這個指令,但由於我們現在要嘗試模組的情境感知版本,我們應該將它設定為更寬鬆的設定值 `ACCESS_CONF`,這讓我們可以在 <Directory> 和 <Location> 區塊內使用指令。若要進一步控管指令的置放,你可以將下列限制結合起來以形成特定規則
RSRC_CONF
:允許在 <Directory> 或 <Location> 外的 .conf 檔案(不是 .htaccess)中ACCESS_CONF
:允許在 <Directory> 或 <Location> 內的 .conf 檔案(不是 .htaccess)中OR_OPTIONS
:當設定 `AllowOverride Options` 時,允許在 .conf 檔案和 .htaccess 中OR_FILEINFO
:當設定 `AllowOverride FileInfo` 時,允許在 .conf 檔案和 .htaccess 中OR_AUTHCFG
:當設定 `AllowOverride AuthConfig` 時,允許在 .conf 檔案和 .htaccess 中OR_INDEXES
:當設定 `AllowOverride Indexes` 時,允許在 .conf 檔案和 .htaccess 中OR_ALL
:允許在 .conf 檔案和 .htaccess 中的任何位置管理您的組態方法更聰明的方式,是讓伺服器協助您建立。為此,我們必須先更改我們的命名標籤,讓伺服器知道,它應該在建立和管理我們的組態時提供協助。由於我們已為我們的模組組態選擇特定目錄(或特定位置)內容,我們將在我們的標籤中加入特定目錄的建立者和合併功能參考
module AP_MODULE_DECLARE_DATA example_module = { STANDARD20_MODULE_STUFF, create_dir_conf, /* Per-directory configuration handler */ merge_dir_conf, /* Merge handler for per-directory configurations */ NULL, /* Per-server configuration handler */ NULL, /* Merge handler for per-server configurations */ directives, /* Any directives we may have for httpd */ register_hooks /* Our hook registering function */ };
現在我們已告訴伺服器協助我們建立和管理組態,我們的步驟是建立一個功能,用來建立新的空白組態。我們這麼做的方式,是建立一個功能(我們剛在我們的標籤中參考過它)作為特定目錄的組態處理程式
void *create_dir_conf(apr_pool_t *pool, char *context) { context = context ? context : "(undefined context)"; example_config *cfg = apr_pcalloc(pool, sizeof(example_config)); if(cfg) { /* Set some default values */ strcpy(cfg->context, context); cfg->enabled = 0; cfg->path = "/foo/bar"; cfg->typeOfAction = 0x11; } return cfg; }
建立內容感知組態的下一步,是合併組態。此階段的程序特別適用於您有父組態和子組態的情境,例如以下範例
<Directory "/var/www"> ExampleEnabled On ExamplePath "/foo/bar" ExampleAction file allow </Directory> <Directory "/var/www/subdir"> ExampleAction file deny </Directory>
在這個範例中,自然而然假設目錄 /var/www/subdir
應該繼承為 /var/www
目錄設定的值,因為我們尚未為這個目錄指定一個 ExampleEnabled
,也沒有指定一個 ExamplePath
。伺服器不會假設知道這是真的,而是聰明地執行以下動作
/var/www
建立新組態/var/www
指定的指令設定組態值/var/www/subdir
建立新組態/var/www/subdir
指定的指令設定組態值/var/www/subdir
的新組態此建議由我們在我們的標籤中參考的 merge_dir_conf
函式處理。此函式的目的是評估兩個組態,並決定它們以何種方式合併
void *merge_dir_conf(apr_pool_t *pool, void *BASE, void *ADD) { example_config *base = (example_config *) BASE ; /* This is what was set in the parent context */ example_config *add = (example_config *) ADD ; /* This is what is set in the new context */ example_config *conf = (example_config *) create_dir_conf(pool, "Merged configuration"); /* This will be the merged configuration */ /* Merge configurations */ conf->enabled = ( add->enabled == 0 ) ? base->enabled : add->enabled ; conf->typeOfAction = add->typeOfAction ? add->typeOfAction : base->typeOfAction; strcpy(conf->path, strlen(add->path) ? add->path : base->path); return conf ; }
現在,我們一起將所有動作整合起來,建立新的內容感知模組。我們首先將建立一個組態,允許我們測試模組如何運作
<Location "/a"> SetHandler example-handler ExampleEnabled on ExamplePath "/foo/bar" ExampleAction file allow </Location> <Location "/a/b"> ExampleAction file deny ExampleEnabled off </Location> <Location "/a/b/c"> ExampleAction db deny ExamplePath "/foo/bar/baz" ExampleEnabled on </Location>
然後,我們將組裝我們的模組代碼。請注意,由于在我們的處理程式中擷取組態時,我們現在將我們的標籤用作參考,所以我已加入一些原型,好讓編譯器滿意
/*$6 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * mod_example_config.c +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ #include <stdio.h> #include "apr_hash.h" #include "ap_config.h" #include "ap_provider.h" #include "httpd.h" #include "http_core.h" #include "http_config.h" #include "http_log.h" #include "http_protocol.h" #include "http_request.h" /*$1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Configuration structure ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ typedef struct { char context[256]; char path[256]; int typeOfAction; int enabled; } example_config; /*$1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Prototypes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ static int example_handler(request_rec *r); const char *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg); const char *example_set_path(cmd_parms *cmd, void *cfg, const char *arg); const char *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char *arg2); void *create_dir_conf(apr_pool_t *pool, char *context); void *merge_dir_conf(apr_pool_t *pool, void *BASE, void *ADD); static void register_hooks(apr_pool_t *pool); /*$1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Configuration directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ static const command_rec directives[] = { AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, ACCESS_CONF, "Enable or disable mod_example"), AP_INIT_TAKE1("examplePath", example_set_path, NULL, ACCESS_CONF, "The path to whatever"), AP_INIT_TAKE2("exampleAction", example_set_action, NULL, ACCESS_CONF, "Special action value!"), { NULL } }; /*$1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Our name tag ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ module AP_MODULE_DECLARE_DATA example_module = { STANDARD20_MODULE_STUFF, create_dir_conf, /* Per-directory configuration handler */ merge_dir_conf, /* Merge handler for per-directory configurations */ NULL, /* Per-server configuration handler */ NULL, /* Merge handler for per-server configurations */ directives, /* Any directives we may have for httpd */ register_hooks /* Our hook registering function */ }; /* ======================================================================================================================= Hook registration function ======================================================================================================================= */ static void register_hooks(apr_pool_t *pool) { ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST); } /* ======================================================================================================================= Our example web service handler ======================================================================================================================= */ static int example_handler(request_rec *r) { if(!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED); /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ example_config *config = (example_config *) ap_get_module_config(r->per_dir_config, &example_module); /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ ap_set_content_type(r, "text/plain"); ap_rprintf(r, "Enabled: %u\n", config->enabled); ap_rprintf(r, "Path: %s\n", config->path); ap_rprintf(r, "TypeOfAction: %x\n", config->typeOfAction); ap_rprintf(r, "Context: %s\n", config->context); return OK; } /* ======================================================================================================================= Handler for the "exampleEnabled" directive ======================================================================================================================= */ const char *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ example_config *conf = (example_config *) cfg; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ if(conf) { if(!strcasecmp(arg, "on")) conf->enabled = 1; else conf->enabled = 0; } return NULL; } /* ======================================================================================================================= Handler for the "examplePath" directive ======================================================================================================================= */ const char *example_set_path(cmd_parms *cmd, void *cfg, const char *arg) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ example_config *conf = (example_config *) cfg; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ if(conf) { strcpy(conf->path, arg); } return NULL; } /* ======================================================================================================================= Handler for the "exampleAction" directive ; Let's pretend this one takes one argument (file or db), and a second (deny or allow), ; and we store it in a bit-wise manner. ======================================================================================================================= */ const char *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char *arg2) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ example_config *conf = (example_config *) cfg; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ if(conf) { { if(!strcasecmp(arg1, "file")) conf->typeOfAction = 0x01; else conf->typeOfAction = 0x02; if(!strcasecmp(arg2, "deny")) conf->typeOfAction += 0x10; else conf->typeOfAction += 0x20; } } return NULL; } /* ======================================================================================================================= Function for creating new configurations for per-directory contexts ======================================================================================================================= */ void *create_dir_conf(apr_pool_t *pool, char *context) { context = context ? context : "Newly created configuration"; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ example_config *cfg = apr_pcalloc(pool, sizeof(example_config)); /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ if(cfg) { { /* Set some default values */ strcpy(cfg->context, context); cfg->enabled = 0; memset(cfg->path, 0, 256); cfg->typeOfAction = 0x00; } } return cfg; } /* ======================================================================================================================= Merging function for configurations ======================================================================================================================= */ void *merge_dir_conf(apr_pool_t *pool, void *BASE, void *ADD) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ example_config *base = (example_config *) BASE; example_config *add = (example_config *) ADD; example_config *conf = (example_config *) create_dir_conf(pool, "Merged configuration"); /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ conf->enabled = (add->enabled == 0) ? base->enabled : add->enabled; conf->typeOfAction = add->typeOfAction ? add->typeOfAction : base->typeOfAction; strcpy(conf->path, strlen(add->path) ? add->path : base->path); return conf; }
我們已查看如何建立 Apache HTTP Server 2.4 的簡單模組,並設定它們。您的下一步完全由您決定,但我希望閱讀本文件對您有所幫助。如果您對如何進一步開發模組有任何疑問,歡迎加入我們的 郵寄清單,或查看其他文件,以獲得更多提示。
typedef struct { const char *key; const char *value; } keyValuePair; keyValuePair *readPost(request_rec *r) { apr_array_header_t *pairs = NULL; apr_off_t len; apr_size_t size; int res; int i = 0; char *buffer; keyValuePair *kvp; res = ap_parse_form_data(r, NULL, &pairs, -1, HUGE_STRING_LEN); if (res != OK || !pairs) return NULL; /* Return NULL if we failed or if there are is no POST data */ kvp = apr_pcalloc(r->pool, sizeof(keyValuePair) * (pairs->nelts + 1)); while (pairs && !apr_is_empty_array(pairs)) { ap_form_pair_t *pair = (ap_form_pair_t *) apr_array_pop(pairs); apr_brigade_length(pair->value, 1, &len); size = (apr_size_t) len; buffer = apr_palloc(r->pool, size + 1); apr_brigade_flatten(pair->value, buffer, &size); buffer[len] = 0; kvp[i].key = apr_pstrdup(r->pool, pair->name); kvp[i].value = buffer; i++; } return kvp; } static int example_handler(request_rec *r) { /*~~~~~~~~~~~~~~~~~~~~~~*/ keyValuePair *formData; /*~~~~~~~~~~~~~~~~~~~~~~*/ formData = readPost(r); if (formData) { int i; for (i = 0; &formData[i]; i++) { if (formData[i].key && formData[i].value) { ap_rprintf(r, "%s = %s\n", formData[i].key, formData[i].value); } else if (formData[i].key) { ap_rprintf(r, "%s\n", formData[i].key); } else if (formData[i].value) { ap_rprintf(r, "= %s\n", formData[i].value); } else { break; } } } return OK; }
static int example_handler(request_rec *r) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ const apr_array_header_t *fields; int i; apr_table_entry_t *e = 0; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ fields = apr_table_elts(r->headers_in); e = (apr_table_entry_t *) fields->elts; for(i = 0; i < fields->nelts; i++) { ap_rprintf(r, "%s: %s\n", e[i].key, e[i].val); } return OK; }
static int util_read(request_rec *r, const char **rbuf, apr_off_t *size) { /*~~~~~~~~*/ int rc = OK; /*~~~~~~~~*/ if((rc = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) { return(rc); } if(ap_should_client_block(r)) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ char argsbuffer[HUGE_STRING_LEN]; apr_off_t rsize, len_read, rpos = 0; apr_off_t length = r->remaining; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ *rbuf = (const char *) apr_pcalloc(r->pool, (apr_size_t) (length + 1)); *size = length; while((len_read = ap_get_client_block(r, argsbuffer, sizeof(argsbuffer))) > 0) { if((rpos + len_read) > length) { rsize = length - rpos; } else { rsize = len_read; } memcpy((char *) *rbuf + rpos, argsbuffer, (size_t) rsize); rpos += rsize; } } return(rc); } static int example_handler(request_rec *r) { /*~~~~~~~~~~~~~~~~*/ apr_off_t size; const char *buffer; /*~~~~~~~~~~~~~~~~*/ if(util_read(r, &buffer, &size) == OK) { ap_rprintf(r, "We read a request body that was %" APR_OFF_T_FMT " bytes long", size); } return OK; }
可用語言: en