<-
Apache > HTTP Server > 文件 > 2.4 版 > 開發人員

為 Apache HTTP Server 2.4 開發模組

可用語言:  en 

本文說明如何為 Apache HTTP Server 2.4 開發模組

Support Apache!

請另見

top

簡介

本文將討論的內容

本文將探討如何為 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
top

定義模組

Module name tags
每個模組都從同一個宣告開始,或如果您愿意的話,名稱標籤會將模組定義為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 服務。我們將會在後面的文件中回歸所有這些元素。

top

入門:連接至伺服器

關於掛勾簡介

在 Apache HTTP Server 2.4 中處理請求時,您首先需要對請求處理流程建立掛勾。掛勾基本上是給伺服器的訊息,告訴伺服器您願意處理或至少檢視客戶端提供的特定請求。所有處理常式,不管是 mod_rewrite、mod_authn_ *、mod_proxy 等,都掛接至請求流程的特定部分。您可能知道,模組會服務於不同目的;有些是驗證/授權處理常式,有些是檔案或指令碼處理常式,而有些第三模組會改寫 URI 或代理程式內容。此外,最後還是取決於伺服器的使用者如何以及何時套用每個模組。因此,伺服器本身不會假設哪個模組負責處理特定請求,並且會詢問每個模組是否對特定請求感興趣。然後再交由每個模組溫和地拒絕處理請求、接受處理請求或徹底拒絕請求被處理,如同驗證/授權模組所做的那樣
Hook handling in httpd
為了讓類似 mod_example 的處理程式更容易得知伺服器是否應處理客戶端請求的內容,伺服器會提供指示,提示模組是否需要協助。其中兩個指示為 AddHandlerSetHandler。我們來看看使用 AddHandler 的範例。在我們的範例情況中,我們希望由 mod_example 處理所有以 .sum 結尾的請求,因此我們會新增一個組態指令,指示伺服器執行此動作

AddHandler example-handler .sum

伺服器得知的訊息如下:每當我們收到 URI 結尾為 .sum 的請求,我們就會讓所有模組知道我們正在尋找名為「example-handler」的名稱。因此,當伺服器處理以 .sum 結尾的請求時,伺服器會讓所有模組知道此請求應該由「example-handler」處理。稍後在我們開始建置 mod_example 時,我們會檢查由 AddHandler 轉傳的這個處理標記,並根據這個標記的值回覆伺服器。

掛接到 httpd

首先,我們僅想要建立一個簡單的處理程式,當請求特定 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 參照是會處理請求的函式。我們將在下一章討論如何建立處理程式。

其他有用的掛鉤

掛接到請求處理階段只是你可以建立的眾多掛鉤之一。其他一些掛鉤方式如下

top

建立處理常式

處理常式基本上是一個當伺服器接收到要求時會接收回呼的函式。傳遞當前要求的記錄(它是如何建立的、傳遞哪些標頭和要求、誰提出要求等),負責告訴伺服器它對要求沒興趣或使用提供的工具處理要求。

一個簡單的「Hello, world!」處理常式

讓我們從建立一個很簡單的要求處理常式開始,它執行以下動作

  1. 檢查這是否是應由「example-handler」處理的要求
  2. 將我們輸出的內容類型設為 text/html
  3. 向客戶端瀏覽器寫回「Hello, world!」
  4. 讓伺服器知道我們處理了這個要求,而且一切順利

在 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 結構

任何要求最重要的部分就是要求記錄 。在呼叫處理常式函式時,這是由在每次呼叫中傳遞的 request_rec* 結構表示的。這個結構通常在模組中稱為 r,包含了模組處理所有 HTTP 要求並做出適當回應所需的所有資訊。

request_rec 結構的幾個主要元素如下

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,讓伺服器知道它應該停止對此要求的所有活動,並繼續執行下一個要求,而不通知其他處理程式。
一般回應碼

HTTP 專用回傳碼(摘錄)

一些您應該知道的有用函式

記憶體管理

在 Apache HTTP 伺服器 2.4 中,由於記憶體池系統,管理資源相當容易。基本上,每個伺服器、連線和要求,都有自己的記憶體池,這些記憶體池會在作用範圍結束,例如要求完成時或伺服器處理關閉時,進行清理。模組只需要連結到此記憶體池,就不需要擔心後續的清理作業──非常棒,對吧?

我們的模組將主要為每個要求配置記憶體,一建立新的物件就使用 r->pool 參考是適宜的作法。在池中配置記憶體的函式包括:

將這些函式寫入範例處理函式中

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

top

加入組態選項

在本文文件的下一個部份中,我們將把焦點從摘要模組轉移開,並建立一個新的範例模組,其唯一的目的是寫出其自己的組態。目的在於檢視伺服器如何使用組態,以及當你開始為模組撰寫進階組態時會發生什麼事。

組態指令簡介

如果你正在閱讀本文件,那麼你可能已經知道什麼是組態指令。簡而言之,指令是一種告知個別模組(或一群模組)如何運作的方式,例如這些指令控制 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 }
};

Directives structure
正如你所見,每個指令都需要設定至少 5 個參數

  1. AP_INIT_TAKE1:這是一個告訴伺服器此指令需要一個且僅有一個參數的巨集。如果我們需要兩個參數,我們可以使用巨集 AP_INIT_TAKE2 等(更多巨集請參閱 httpd_conf.h)。
  2. exampleEnabled:這是指令的名稱。更精確來說,這是使用者必須在設定檔中指定的內容,才能在模組中呼叫組態變更。
  3. example_set_enabled:這是解析指令並適當地設定組態的 C 函式參考。我們將在下一段討論製作此函式的步驟。
  4. RSRC_CONF:告訴伺服器可以在哪裡使用此指令。我們將在後面的章節詳細說明此值,但目前而言,RSRC_CONF 表示伺服器將只在伺服器情境中接受這些指令。
  5. "啟用或停用....":這只是簡要說明指令的功能。

定義中「遺失」的參數通常設定為 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,我們就會發現組態已經調整為我們在組態檔案中撰寫的內容。

top

感知脈絡的組態

感知脈絡的組態簡介

在 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 設定兩個不同的脈絡

  1. /var/www 中,所有 http://example.com 的要求都必須轉到 http://www.example.com
  2. /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> 區塊內使用指令。若要進一步控管指令的置放,你可以將下列限制結合起來以形成特定規則

使用伺服器配置設定區段

管理您的組態方法更聰明的方式,是讓伺服器協助您建立。為此,我們必須先更改我們的命名標籤,讓伺服器知道,它應該在建立和管理我們的組態時提供協助。由於我們已為我們的模組組態選擇特定目錄(或特定位置)內容,我們將在我們的標籤中加入特定目錄的建立者和合併功能參考

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。伺服器不會假設知道這是真的,而是聰明地執行以下動作

  1. /var/www 建立新組態
  2. 根據為 /var/www 指定的指令設定組態值
  3. /var/www/subdir 建立新組態
  4. 根據為 /var/www/subdir 指定的指令設定組態值
  5. 建議將兩個組態合併成 /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;
}
top

總計

我們已查看如何建立 Apache HTTP Server 2.4 的簡單模組,並設定它們。您的下一步完全由您決定,但我希望閱讀本文件對您有所幫助。如果您對如何進一步開發模組有任何疑問,歡迎加入我們的 郵寄清單,或查看其他文件,以獲得更多提示。

top

一些有用的程式碼片段

從 POST 表單資料擷取變數

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;
}

列印接收到的每個 HTTP 標頭

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 

top

意見

注意事項
這不是問答區。放在這的留言應以建議改善文件或伺服器為關鍵,如果已實作或被視為無效/離題,可能會被我們的管理員移除。關於如何管理 Apache HTTP Server 的問題,應轉向我們在 Libera.chat 的 IRC 頻道 #httpd,或寄送至我們的郵遞清單