catalogphp
0. 引言 1. windows下開發apache模塊 2. mod進階: 接收客戶端數據的 echo 模塊 3. mod進階: 可配置的 echo 模塊 4. mod進階: 過濾器
0. 引言html
Apache httpd 從 2.0 以後,已經不只僅侷限於一個 http 的服務器,更是一個完善而強大,靈活而健壯,且容易擴展的開發平臺。開發人員經過定製 Apache 模塊,能夠幾乎無限制的擴展 Apache httpd,使其更好的與實際應用場景匹配,而又無需考慮底層的網絡傳輸細節。這樣既能夠提升開發效率,節省開發成本,又能充分利用 Apache 自己的健壯性及可靠性python
0x1: Apache httpd 開發平臺簡介linux
Apache httpd 自 2.0 以後,針對 1.0 作了大量的改進,包括對自身內核的改造,擴展機制的改進,APR(Apache Portable Runtime) 的剝離(使得 Apache 成爲一個真正意義的跨平臺服務器)。Apache 2.0 成爲一個很容易擴展的開發平臺數據庫
Apache 中包含了大量的擴展模塊(module),如 mod_cgi 用以處理 cgi 腳本、mod_perl 用以處理 perl 腳本,將 perl 的能力與 Apache httpd 結合起來等等。用戶能夠經過必定的開發標準(接口規範)來開發符合本身業務場景的模塊,並動態的加載到 Apache 中,Apache 會根據配置文件中的規則來定位,調用模塊,完成客戶端請求,簡單來講,apache的擴展編寫能夠分類一下幾類apache
1. apache httpd 模塊: 編寫模塊每每要比編寫過濾器要複雜一點,能夠當作是一種原始的apache擴展編寫方式,可是靈活性更高 2. 輸出過濾器: apache在模塊的基礎上進行了API封裝,使得過濾器的代碼編寫變得更簡單 3. 輸入過濾器
0x2: Apache httpd 模塊機制windows
Apache httpd 由一個內核和大量的模塊組成,包括用以加載其餘模塊的功能單元自身也是一個模塊,模塊是Apache擴展的基礎機制(理論上模塊能夠實現任何功能)。通常而言,一個 HTTP 服務器的工做序列是這樣的數組
1. 接受客戶端請求 多是請求一個部署在 HTTP 服務器程序可訪問的文件,讀取該文件做爲響應返回,咱們在瀏覽器的地址欄中輸入相似這樣的 URL:http://host/index.html,瀏覽器將會嘗試與 host 指定的 HTTP 服務器的 80 端口創建鏈接,若是成功,則發送 HTTP 請求,獲取 index.html 頁面。若是成功,則在瀏覽器中解析該 HTML 文件 這種工做方式在靜態頁面的場景下沒有任何問題。可是實際應用每每會與數據庫交互,動態生成頁面內容。如服務端較爲流行的 cgi/php 腳本等。這就須要更高級,更靈活的內容生成器作支持 2. 預處理 1) 權限校驗 2) HTTP 頭信息識別等 3. 內容生成 經過與操做系統其餘資源交互 ( 如文件讀寫,數據庫訪問等 ) 來完成動態內容的生成 4. 其餘善後操做等 進行日誌記錄,資源釋放等操做
一般註冊模塊以處理配置文件中的特殊文件類型或其它此類標準瀏覽器
AddHandler cgi-script .cgi //php、python都是經過配置指定特定後綴擴展名的處理模塊
Apache 爲每一個請求調用全部處理程序,所以每一個處理程序應該迅速決定請求是不是衝着它來的。所以,大多數頭文件都從相似下面的語句開始安全
if (!req->handler || strcmp(req->handler, "target-module")) return DECLINED;
0x3: Apache 2.0以後的過濾器機制
在大多數時候,apache擴展開發者並不須要很複雜的收包/發包邏輯,而僅僅須要對HTTP Header、Body進行檢測,爲了提升效率,apache開發者對模塊代碼進行了API封裝
Apache 2.0 有專門的 API 用於開發模塊,這些模塊只需修改對用戶響應的內容,或者只需修改用戶的 HTTP 請求的詳細信息。這些 API 分別被稱爲
1. 輸出過濾器 輸出過濾器最爲常見,一個好的示例是標準 Apache 2.0 模塊,它被用於計算返回給用戶的內容的長度以便更新適當的頭和日誌項。另外一個示例是用於對出站內容進行自動拼寫檢查的模塊 2. 輸入過濾器 典型的WEB WAF
從嚴格意義上來講,基於WEB SERVER的Mod(擴展模塊)WAF是一種HTTP全生命週期的檢測/過濾/攔截/記錄機制,它須要綜合利用"輸出過濾器"、"輸入過濾器"
Relevant Link:
http://www.ibm.com/developerworks/cn/opensource/os-cn-apachehttpd/
1. windows下開發apache模塊
0x1: windows下安裝apache
http://apache.dataguru.cn//httpd/binaries/win32/ //必定要custom所有安裝,不然就不會有include和lib目錄
0x2: 安裝Perl
將要使用的apx包要用到perl解析編譯,因此,需先安裝perl
http://www.activestate.com/activeperl
0x3: 安裝apxs
1. http://www.apachelounge.com/download/apxs_win32.zip 2. 解壓到: D:\apxs 3. pushd D:\apxs 4. perl Configure.pl --with-apache2=D:\wamp\bin\apache\apache2.4.9 --with-apache-prog=httpd.exe /* apxs.bat has been created under D:\wamp\bin\apache\APACHE~1.9\bin. apr-1-config.pl.bat has been created under D:\wamp\bin\apache\APACHE~1.9\bin. apu-1-config.pl.bat has been created under D:\wamp\bin\apache\APACHE~1.9\bin. */ 5. pushd D:\wamp\bin\apache\apache2.4.9\bin 6. apxs(出現下列提示則說明安裝成功) /* Use of assignment to $[ is deprecated at apxs.bat line 120. Usage: apxs -g [-S <var>=<val>] -n <modname> apxs -q [-v] [-S <var>=<val>] <query> ... apxs -c [-S <var>=<val>] [-o <dsofile>] [-D <name>[=<value>]] [-I <incdir>] [-L <libdir>] [-l <libname>] [-Wc,<flags>] [-Wl,<flags>] [-p] <files> ... apxs -i [-S <var>=<val>] [-a] [-A] [-n <modname>] <dsofile> ... apxs -e [-S <var>=<val>] [-a] [-A] [-n <modname>] <dsofile> ... */ 7. 配置apxs編譯環境 D:\wamp\bin\apache\apache2.4.9\build\config_vars.mk CC = D:\CODEBL~1\MinGW\bin\gcc.exe 改成: CC = cl.exe LD = D:\CODEBL~1\MinGW\bin\g++.exe 改成: LD = link.exe CPP = gcc -E 改成: CPP = LDFLAGS = kernel32.lib /nologo /subsystem:windows /dll /machine:I386 /libpath:"D:\wamp\bin\apache\APACHE~1.9\lib" 改成: LDFLAGS = kernel32.lib /nologo /subsystem:windows /dll /machine:X64 /libpath:"D:\wamp\bin\apache\APACHE~1.9\lib" 8. 使用apxs生成mod框架模版 Visual Studio 命令提示(2010) pushd D:\安所有工做\服務器waf模塊mod研究 D:\wamp\bin\apache\apache2.4.9\bin\apxs -g -n helloworld /* Use of assignment to $[ is deprecated at D:\wamp\bin\apache\apache2.4.9\bin\apxs.bat line 120. Creating [DIR] helloworld Creating [FILE] helloworld/Makefile Creating [FILE] helloworld/mod_helloworld.c Creating [FILE] helloworld/.deps */ 9. 進入helloworld目錄,編輯mod_helloworld.c(這就是咱們要開發的內容) cd helloworld D:\wamp\bin\apache\apache2.4.9\bin\apxs -c -i -a mod_helloworld.c libapr-1.lib libaprutil-1.lib libapriconv-1.lib libhttpd.lib 10. 將mod_helloworld.so拷貝到Apache2.2\modules下 11. 打開conf文件夾下的httpd.conf文件 /* LoadModule helloworld_module "D:/wamp/bin/apache/APACHE~1.9/modules/mod_helloworld.so" <Location /helloworld> SetHandler helloworld </Location> */ 12. 重啓apache 13. http://localhost/helloworld
0x4: mod通用模板代碼框架
#include "httpd.h" #include "http_config.h" #include "http_protocol.h" #include "ap_config.h" /* The sample content handler 首先須要一個實際處理客戶端請求的函數 (handler),命名方式通常爲」模塊名 _handler」,接收一個 request_rec 類型的指針,並返回一個 int 類型的狀態值 request_rec 指針中包括全部的客戶端鏈接信息及 Apache 內部的指針,如鏈接信息表,內存池等,這個結構相似於 J2EE 開發中 servlet 的 HttpRequest 對象及 HttpResponse 對象。經過 request_rec,咱們能夠讀取客戶端請求數據 / 寫入響應數據,獲取請求中的信息 ( 如客戶端瀏覽器類型,編碼方式等 ) */ static int helloworld_handler(request_rec *r) { if (strcmp(r->handler, "helloworld")) { return DECLINED; } r->content_type = "text/html"; if (!r->header_only) ap_rputs("The sample page from mod_helloworld.c\n", r); return OK; } //註冊函數,通常命名爲」模塊名 _register_hooks」,傳入參數爲 Apache 的內存池指針。這個函數用於通知 Apache 在什麼時候,以何種方式註冊響應函數 (handler) static void helloworld_register_hooks(apr_pool_t *p) { ap_hook_handler(helloworld_handler, NULL, NULL, APR_HOOK_MIDDLE); } /* Dispatch list for API hooks 模塊的定義,Apache 模塊加載器經過這個結構體中的定義來在適當的時刻調用適當的函數以處理響應。應該注意的是,第一個成員默認填寫爲 STANDARD20_MODULE_STUFF,最後一個成員爲註冊函數 */ module AP_MODULE_DECLARE_DATA helloworld_module = { STANDARD20_MODULE_STUFF, NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ NULL, /* table of config file commands */ helloworld_register_hooks /* register hooks */ };
0x5: apache mod核心數據結構: request_rec
The request_rec request record is the heart and soul of the Apache API. It contains everything you could ever want to know about the current request and then some.
\apache2.4.9\include\httpd.h
/** * @brief A structure that represents the current request */ struct request_rec { /** The pool associated with the request This is a resource pool that is valid for the lifetime of the request. Your request-time handlers should allocate memory from this pool. */ apr_pool_t *pool; /** The connection to the client This is a pointer to the connection record for the current request, from which you can derive information about the local and remote host addresses, as well as the username used during authentication */ conn_rec *connection; /** The virtual host for this request This is a pointer to a server record server_rec structure, from which you can gather information about the current server. */ server_rec *server; /* Under various circumstances, including subrequests and internal redirects, Apache will generate one or more subrequests that are identical in all respects to an ordinary request. When this happens, these fields are used to chain the subrequests into a linked list. 1. The next field points to the more recent request (or NULL, if there is none), 2. and the prev field points to the immediate ancestor of the request. 3. main points back to the top-level request. */ /** Pointer to the redirected request if this is an external redirect */ request_rec *next; /** Pointer to the previous request if this is an internal redirect */ request_rec *prev; /** Pointer to the main request if this is a sub-request * (see http_request.h) */ request_rec *main; /* Info about the request itself... we begin with stuff that only * protocol.c should ever touch... */ /** First line of request This contains the first line of the request, for logging purposes. */ char *the_request; /** HTTP/0.9, "simple" request (e.g. GET /foo\n w/no headers) */ int assbackwards; /** A proxy request (calculated during post_read_request/translate_name) possible values PROXYREQ_NONE, PROXYREQ_PROXY, PROXYREQ_REVERSE, PROXYREQ_RESPONSE If the current request is a proxy request, then this field will be set to a true (nonzero) value. Note that mod_proxy or mod_perl must be configured with the server for automatic proxy request detection. You can also set it yourself in order to activate Apache's proxy mechanism */ int proxyreq; /** HEAD request, as opposed to GET This field will be true if the remote client made a head-only request (i.e., HEAD). You should not change the value of this field. */ int header_only; /** Protocol version number of protocol; 1.1 = 1001 */ int proto_num; /** Protocol string, as given to us, or HTTP/0.9 This field contains the name and version number of the protocol requested by the browser, for example HTTP/1.0. */ char *protocol; /** Host, as set by full URI or Host: This contains the name of the host requested by the client, either within the URI (during proxy requests) or in the Host header. The value of this field may not correspond to the canonical name of your server or the current virtual host but can be any of its DNS aliases. For this reason, it is better to use the ap_get_server_name() API function call described under "Processing Requests." hostname訪問可能直接DNS域名訪問 */ const char *hostname; /** Time when the request started This is the time that the request started as a C time_t structure. */ apr_time_t request_time; /** Status line, if set by script This field holds the full text of the status line returned from Apache to the remote browser, for example 200 OK. Ordinarily you will not want to change this directly but will allow Apache to set it based on the return value from your handler. However, you can change it directly in the rare instance that you want your handler to lie to Apache about its intentions (e.g., tell Apache that the handler processed the transaction OK, but send an error message to the browser). */ const char *status_line; /** Status line */ int status; /* Request method, two ways; also, protocol, etc.. Outside of protocol.c, * look, but don't touch. */ /** M_GET, M_POST, etc. */ int method_number; /** Request method (eg. GET, HEAD, POST, etc.) */ const char *method; /** * 'allowed' is a bitvector of the allowed methods. * * A handler must ensure that the request method is one that * it is capable of handling. Generally modules should DECLINE * any request methods they do not handle. Prior to aborting the * handler like this the handler should set r->allowed to the list * of methods that it is willing to handle. This bitvector is used * to construct the "Allow:" header required for OPTIONS requests, * and HTTP_METHOD_NOT_ALLOWED and HTTP_NOT_IMPLEMENTED status codes. * * Since the default_handler deals with OPTIONS, all modules can * usually decline to deal with OPTIONS. TRACE is always allowed, * modules don't need to set it explicitly. * * Since the default_handler will always handle a GET, a * module which does *not* implement GET should probably return * HTTP_METHOD_NOT_ALLOWED. Unfortunately this means that a Script GET * handler can't be installed by mod_actions. */ apr_int64_t allowed; /** Array of extension methods */ apr_array_header_t *allowed_xmethods; /** List of allowed methods */ ap_method_list_t *allowed_methods; /** byte count in stream is for body */ apr_off_t sent_bodyct; /** body byte count, for easy access */ apr_off_t bytes_sent; /** Last modified time of the requested resource */ apr_time_t mtime; /* HTTP/1.1 connection-level features */ /** The Range: header */ const char *range; /** The "real" content length */ apr_off_t clength; /** sending chunked transfer-coding */ int chunked; /** Method for reading the request body * (eg. REQUEST_CHUNKED_ERROR, REQUEST_NO_BODY, * REQUEST_CHUNKED_DECHUNK, etc...) */ int read_body; /** reading chunked transfer-coding */ int read_chunked; /** is client waiting for a 100 response? */ unsigned expecting_100; /** The optional kept body of the request. */ apr_bucket_brigade *kept_body; /** For ap_body_to_table(): parsed body */ /* XXX: ap_body_to_table has been removed. Remove body_table too or * XXX: keep it to reintroduce ap_body_to_table without major bump? */ apr_table_t *body_table; /** Remaining bytes left to read from the request body */ apr_off_t remaining; /** Number of bytes that have been read from the request body */ apr_off_t read_length; /* 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). * * The 'notes' apr_table_t is for notes from one module to another, with no * other set purpose in mind... */ /** MIME header environment from the request */ apr_table_t *headers_in; /** MIME header environment for the response */ apr_table_t *headers_out; /** MIME header environment for the response, printed even on errors and * persist across internal redirects */ apr_table_t *err_headers_out; /** Array of environment variables to be used for sub processes */ apr_table_t *subprocess_env; /** Notes from one module to another */ apr_table_t *notes; /* content_type, handler, content_encoding, and all content_languages * MUST be lowercased strings. They may be pointers to static strings; * they should not be modified in place. */ /** The content-type for the current request */ const char *content_type; /* Break these out --- we dispatch on 'em */ /** The handler string that we use to call a handler function */ const char *handler; /* What we *really* dispatch on */ /** How to encode the data */ const char *content_encoding; /** Array of strings representing the content languages */ apr_array_header_t *content_languages; /** variant list validator (if negotiated) */ char *vlist_validator; /** If an authentication check was made, this gets set to the user name. */ char *user; /** If an authentication check was made, this gets set to the auth type. */ char *ap_auth_type; /* What object is being requested (either directly, or via include * or content-negotiation mapping). */ /** The URI without any parsing performed */ char *unparsed_uri; /** The path portion of the URI, or "/" if no path provided */ char *uri; /** The filename on disk corresponding to this response */ char *filename; /* XXX: What does this mean? Please define "canonicalize" -aaron */ /** The true filename, we canonicalize r->filename if these don't match */ char *canonical_filename; /** The PATH_INFO extracted from this request */ char *path_info; /** The QUERY_ARGS extracted from this request */ char *args; /** * Flag for the handler to accept or reject path_info on * the current request. All modules should respect the * AP_REQ_ACCEPT_PATH_INFO and AP_REQ_REJECT_PATH_INFO * values, while AP_REQ_DEFAULT_PATH_INFO indicates they * may follow existing conventions. This is set to the * user's preference upon HOOK_VERY_FIRST of the fixups. */ int used_path_info; /** A flag to determine if the eos bucket has been sent yet */ int eos_sent; /* 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). */ /** Options set in config files, etc. */ struct ap_conf_vector_t *per_dir_config; /** Notes on *this* request */ struct ap_conf_vector_t *request_config; /** Optional request log level configuration. Will usually point * to a server or per_dir config, i.e. must be copied before * modifying */ const struct ap_logconf *log; /** Id to identify request in access and error log. Set when the first * error log entry for this request is generated. */ const char *log_id; /** * A linked list of the .htaccess configuration directives * accessed by this request. * N.B. always add to the head of the list, _never_ to the end. * that way, a sub request's list can (temporarily) point to a parent's list */ const struct htaccess_result *htaccess; /** A list of output filters to be used for this request */ struct ap_filter_t *output_filters; /** A list of input filters to be used for this request */ struct ap_filter_t *input_filters; /** A list of protocol level output filters to be used for this * request */ struct ap_filter_t *proto_output_filters; /** A list of protocol level input filters to be used for this * request */ struct ap_filter_t *proto_input_filters; /** This response can not be cached */ int no_cache; /** There is no local copy of this response */ int no_local_copy; /** Mutex protect callbacks registered with ap_mpm_register_timed_callback * from being run before the original handler finishes running */ apr_thread_mutex_t *invoke_mtx; /** A struct containing the components of URI */ apr_uri_t parsed_uri; /** finfo.protection (st_mode) set to zero if no such file */ apr_finfo_t finfo; /** remote address information from conn_rec, can be overridden if * necessary by a module. * This is the address that originated the request. */ apr_sockaddr_t *useragent_addr; char *useragent_ip; };
Relevant Link:
http://www.cnblogs.com/QRcode/p/3193397.html http://blog.csdn.net/hxsstar/article/details/19820029 https://publib.boulder.ibm.com/iseries/v5r1/ic2924/info/rzaie/APR/structrequest__rec.html http://docstore.mik.ua/orelly/apache_mod/128.htm http://blog.csdn.net/wind_cludy/article/details/6557776 http://docstore.mik.ua/orelly/apache_mod/128.htm#listing10_1
2. mod進階: 接收客戶端數據的 echo 模塊
若是Apache模塊只能產生內容,那麼使用普通的HTML文件(即便用httpd默認的內容生成器)也能夠完成。模塊存在的意義在於,它能夠輕鬆地處理客戶端傳遞的數據,並將這些數據加工,而後響應客戶端請求
/* ** mod_helloworld.c -- Apache sample helloworld module ** [Autogenerated via ``apxs -n helloworld -g''] ** ** To play with this sample module first compile it into a ** DSO file and install it into Apache's modules directory ** by running: ** ** $ apxs -c -i mod_helloworld.c ** ** Then activate it in Apache's httpd.conf file for instance ** for the URL /helloworld in as follows: ** ** # httpd.conf ** LoadModule helloworld_module modules/mod_helloworld.so ** <Location /helloworld> ** SetHandler helloworld ** </Location> ** ** Then after restarting Apache via ** ** $ apachectl restart ** ** you immediately can request the URL /helloworld and watch for the ** output of this module. This can be achieved for instance via: ** ** $ lynx -mime_header http://localhost/helloworld ** ** The output should be similar to the following one: ** ** HTTP/1.1 200 OK ** Date: Tue, 31 Mar 1998 14:42:22 GMT ** Server: Apache/1.3.4 (Unix) ** Connection: close ** Content-Type: text/html ** ** The sample page from mod_helloworld.c */ #include "httpd.h" #include "http_config.h" #include "http_protocol.h" #include "ap_config.h" #define DFT_BUF_SIZE 1024 /** * @brief read_post_data 從 request 中獲取 POST 數據到緩衝區 * * @param req apache request_rec 對象 * @param post 接收緩衝區 * @param post_size 接收緩衝區長度 * * @return */ static int read_post_data(request_rec *req, char **post, size_t *post_size) { char buffer[DFT_BUF_SIZE] = {0}; size_t bytes, count, offset; bytes = count = offset = 0; if(ap_setup_client_block(req, REQUEST_CHUNKED_DECHUNK) != OK) { return HTTP_BAD_REQUEST; } if(ap_should_client_block(req)) { //經過 Apache 提供的 API:ap_get_client_block 將請求中 POST 的數據讀入到緩衝區 for(bytes = ap_get_client_block(req, buffer, DFT_BUF_SIZE); bytes > 0; bytes = ap_get_client_block(req, buffer, DFT_BUF_SIZE)) { //若是預分配的緩衝區不夠,則從新分配內存存放,並同時修改緩衝區的實際長度 count += bytes; if(count > *post_size) { *post = (char *)realloc(*post, count); if(*post == NULL) { return HTTP_INTERNAL_SERVER_ERROR; } } *post_size = count; offset = count - bytes; memcpy((char *)*post+offset, buffer, bytes); } } else { *post_size = 0; return OK; } return OK; } /* The sample content handler 首先須要一個實際處理客戶端請求的函數 (handler),命名方式通常爲」模塊名 _handler」,接收一個 request_rec 類型的指針,並返回一個 int 類型的狀態值 request_rec 指針中包括全部的客戶端鏈接信息及 Apache 內部的指針,如鏈接信息表,內存池等,這個結構相似於 J2EE 開發中 servlet 的 HttpRequest 對象及 HttpResponse 對象。經過 request_rec,咱們能夠讀取客戶端請求數據 / 寫入響應數據,獲取請求中的信息 ( 如客戶端瀏覽器類型,編碼方式等 ) */ static int helloworld_handler(request_rec *r) { int ret; char *post = NULL; size_t post_size = 0; if (strcmp(r->handler, "helloworld")) { return DECLINED; } //只接收GET、POST請求 if((r->method_number != M_GET) && (r->method_number != M_POST)) { return HTTP_METHOD_NOT_ALLOWED; } post = (char *)malloc(sizeof(char) * DFT_BUF_SIZE); post_size = DFT_BUF_SIZE; if(post == NULL) { return HTTP_INTERNAL_SERVER_ERROR; } memset(post, '\0', post_size); //讀取POST數據 ret = read_post_data(r, &post, &post_size); if(ret != OK) { free(post); post = NULL; post_size = 0; return ret; } ap_set_content_type(r, "text/html;charset=utf-8"); ap_set_content_length(r, post_size); if(post_size == 0) { ap_rputs("no post data found", r); return OK; } ap_rputs(post, r); free(post); post = NULL; post_size = 0; return OK; } //註冊函數,通常命名爲」模塊名 _register_hooks」,傳入參數爲 Apache 的內存池指針。這個函數用於通知 Apache 在什麼時候,以何種方式註冊響應函數 (handler) static void helloworld_register_hooks(apr_pool_t *p) { ap_hook_handler(helloworld_handler, NULL, NULL, APR_HOOK_MIDDLE); } /* Dispatch list for API hooks 模塊的定義,Apache 模塊加載器經過這個結構體中的定義來在適當的時刻調用適當的函數以處理響應。應該注意的是,第一個成員默認填寫爲 STANDARD20_MODULE_STUFF,最後一個成員爲註冊函數 */ module AP_MODULE_DECLARE_DATA helloworld_module = { STANDARD20_MODULE_STUFF, NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ NULL, /* table of config file commands */ helloworld_register_hooks /* register hooks */ };
Relevant Link:
http://www.ibm.com/developerworks/cn/opensource/os-cn-apachehttpd/
3. mod進階: 可配置的 echo 模塊
咱們繼續擴展上例中的 echo_post 模塊,咱們將 echo_post 擴展爲可配置的模塊,經過修改配置文件 httpd.conf 中設置 ConvertType 的值,可使得模塊在運行時的行爲發生變化
0x1: 配置信息讀取
typedef struct{ int convert_type; // 轉換類型 }cust_config_t;
這個結構體僅有一個成員,convert_type, 表示轉換類型,若是在配置文件中該值被設置爲 0,則將客戶端 POST 的數據轉換爲大寫,若是爲 1,則轉換爲小寫。這樣便可經過配置信息修改模塊運行時的行爲
//create_config 函數用以建立一個用戶自定義的結構體 static void *create_config(apr_pool_t *pool, server_rec *server); //set_mod_config 函數用以設置配置結構體中的成員,這個函數註冊在 command_rec 數組中 static const char *set_mod_config(cmd_parms *params, void *config, const char *arg); //而 command_rec 數組則保存在模塊聲明結構體中: 定義一個 command_rec 結構體類型的數組 static const command_rec cust_echo_cmds[] = { AP_INIT_TAKE1("ConvertType", set_mod_config, NULL, RSRC_CONF, "convert type of post data"), {0} }; //註冊模塊回調函數 /* Dispatch list for API hooks */ module AP_MODULE_DECLARE_DATA cust_echo_post_module = { STANDARD20_MODULE_STUFF, NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ create_config, /* create per-server config structures */ NULL, /* merge per-server config structures */ cust_echo_cmds, /* table of config file commands */ cust_echo_post_register_hooks /* register hooks */ };
0x2: Code
/* ** mod_cust_echo_post.c -- Apache sample cust_echo_post module ** [Autogenerated via ``apxs -n cust_echo_post -g''] ** ** To play with this sample module first compile it into a ** DSO file and install it into Apache's modules directory ** by running: ** ** $ apxs -c -i mod_cust_echo_post.c ** ** Then activate it in Apache's httpd.conf file for instance ** for the URL /cust_echo_post in as follows: ** ** # httpd.conf ** LoadModule cust_echo_post_module modules/mod_cust_echo_post.so ** <Location /cust_echo_post> ** SetHandler cust_echo_post ** </Location> ** ** Then after restarting Apache via ** ** $ apachectl restart ** ** you immediately can request the URL /cust_echo_post and watch for the ** output of this module. This can be achieved for instance via: ** ** $ lynx -mime_header http://localhost/cust_echo_post ** ** The output should be similar to the following one: ** ** HTTP/1.1 200 OK ** Date: Tue, 31 Mar 1998 14:42:22 GMT ** Server: Apache/1.3.4 (Unix) ** Connection: close ** Content-Type: text/html ** ** The sample page from mod_cust_echo_post.c */ #include "httpd.h" #include "http_config.h" #include "http_protocol.h" #include "ap_config.h" #define DFT_BUF_SIZE 4096 module AP_MODULE_DECLARE_DATA cust_echo_post_module; static void *create_config(apr_pool_t *pool, server_rec *server); static const char *set_mod_config(cmd_parms *params, void *config, const char *arg); typedef struct { int convert_type; //轉換類型 }cust_config_t; static const command_rec cust_echo_cmds[] = { AP_INIT_TAKE1("ConvertType", set_mod_config, NULL, RSRC_CONF, "convert type of post data"), {0} }; static void *create_config(apr_pool_t *pool, server_rec *server) { cust_config_t *config; config = (cust_config_t *)apr_pcalloc(pool, sizeof(cust_config_t)); return (void *)config; } static const char *set_mod_config(cmd_parms *params, void *conf, const char *arg) { cust_config_t *config = ap_get_module_config(params->server->module_config, &cust_echo_post_module); if(strcmp(params->cmd->name, "ConvertType") == 0) { config->convert_type = atoi((char *)arg); } return NULL; } /** * @brief read_post_data 從request中獲取POST數據到緩衝區 * * @param req apache request_rec對象 * @param post 接收緩衝區 * @param post_size 接收緩衝區長度 * * @return */ static int read_post_data(request_rec *req, char **post, size_t *post_size){ char buffer[DFT_BUF_SIZE] = {0}; size_t bytes, count, offset; bytes = count = offset = 0; if(ap_setup_client_block(req, REQUEST_CHUNKED_DECHUNK) != OK){ return HTTP_BAD_REQUEST; } if(ap_should_client_block(req)){ for(bytes = ap_get_client_block(req, buffer, DFT_BUF_SIZE); bytes > 0; bytes = ap_get_client_block(req, buffer, DFT_BUF_SIZE)){ count += bytes; if(count > *post_size){ *post = (char *)realloc(*post, count); if(*post == NULL){ return HTTP_INTERNAL_SERVER_ERROR; } } *post_size = count; offset = count - bytes; memcpy((char *)*post+offset, buffer, bytes); } }else{ *post_size = 0; return OK; } return OK; } /* The sample content handler */ static int cust_echo_post_handler(request_rec *req) { if (strcmp(req->handler, "cust_echo_post")) { return DECLINED; } if((req->method_number != M_GET) && (req->method_number != M_POST)) { return HTTP_METHOD_NOT_ALLOWED; } char *post = (char *)malloc(sizeof(char)*DFT_BUF_SIZE); size_t post_size = DFT_BUF_SIZE; if(post == NULL) { return HTTP_INTERNAL_SERVER_ERROR; } memset(post, '\0', post_size); int ret = read_post_data(req, &post, &post_size); if(ret != OK) { free(post); post = NULL; post_size = 0; return ret; } ap_set_content_type(req, "text/html;charset=utf-8"); ap_set_content_length(req, post_size); if(post_size == 0) { ap_rputs("no post data found", req); return OK; } cust_config_t *config = ap_get_module_config(req->server->module_config, &cust_echo_post_module); if(config == NULL) { return HTTP_INTERNAL_SERVER_ERROR; } //make a copy of user post data char *converted = strdup(post); int i = 0; //convert it according to convert_type switch(config->convert_type) { case 0: for(i = 0; i < post_size; i++) { converted[i] = toupper(((char *)post[i])); } break; case 1: for(i = 0; i < post_size; i++) { converted[i] = tolower(((char *)post[i])); } break; default: break; } ap_rputs(converted, req); free(converted); converted = NULL; free(post); post = NULL; post_size = 0; return OK; } static void cust_echo_post_register_hooks(apr_pool_t *p) { ap_hook_handler(cust_echo_post_handler, NULL, NULL, APR_HOOK_MIDDLE); } /* Dispatch list for API hooks */ module AP_MODULE_DECLARE_DATA cust_echo_post_module = { STANDARD20_MODULE_STUFF, NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ create_config, /* create per-server config structures */ NULL, /* merge per-server config structures */ cust_echo_cmds, /* table of config file commands */ cust_echo_post_register_hooks /* register hooks */ };
運行可配置 echo 模塊
LoadModule cust_echo_post_module "D:/wamp/bin/apache/APACHE~1.9/modules/mod_cust_echo_post.so" <Location /cust_echo_post> SetHandler cust_echo_post </Location> #configure for cust_echo_post ConvertType 0
LoadModule cust_echo_post_module "D:/wamp/bin/apache/APACHE~1.9/modules/mod_cust_echo_post.so" <Location /cust_echo_post> SetHandler cust_echo_post </Location> #configure for cust_echo_post ConvertType 1
Relevant Link:
http://www.ibm.com/developerworks/cn/opensource/os-cn-apachehttpd/
4. mod進階: 過濾器
過濾器事實上是另外一種形式的模塊,Apache對通用的數據結構都作過一些封裝,並以庫的方式提供(即APR(Apache Portable Runtime))。在過濾器中,有兩個比較重要的數據結構
1. apr_bucket 2. apr_bucket_brigade: apr_bucket_birgade 至關於一個環狀隊列,而 apr_bucket 是隊列中的元素
全部的過濾器造成一個長鏈,數據從上一個過濾器流入,進行過濾,而後將加工過的數據流入下一個過濾器,處理一個 HTTP 事務期間可能會屢次調用某個過濾器,就象不一樣的塊經過「桶隊列」。對於全部最普通的過濾器來講,這意味着過濾器必須可以在兩次調用之間保存某種上下文
咱們的過濾器很是簡單,從上一個過濾器中讀到數據,將數據中的字符串轉換爲大寫,而後將桶 (apr_bucket) 傳遞給下一個過濾器。Apache 提供了豐富的 API 來完成這一系列的操做
0x1: 大小寫轉換過濾器
static apr_status_t case_filter(ap_filter_t *filter, apr_bucket_brigade *bbin) { request_rec *req = filter->r; conn_rec *con = req->connection; apr_bucket *bucket; apr_bucket_brigade *bbout; //create brigade bbout = apr_brigade_create(req->pool, con->bucket_alloc); //iterate the full brigade APR_BRIGADE_FOREACH(bucket, bbin) { if(APR_BUCKET_IS_EOS(bucket) || APR_BUCKET_IS_FLUSH(bucket)) { APR_BUCKET_REMOVE(bucket); APR_BRIGADE_INSERT_TAIL(bbout, bucket); return ap_pass_brigade(filter->next, bbout); } char *data, *buffer; apr_size_t data_len; //read content of current bucket in brigade apr_bucket_read(bucket, &data, &data_len, APR_NONBLOCK_READ); buffer = apr_bucket_alloc(data_len, con->bucket_alloc); int i; for(i = 0; i < data_len; i++) { //convert buffer[i] = apr_toupper(data[i]); } apr_bucket *temp_bucket; temp_bucket = apr_bucket_heap_create(buffer, data_len, apr_bucket_free, con->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bbout, temp_bucket); } return APR_SUCCESS; }
0x2: 註冊過濾器
static void filter_echo_post_register_hooks(apr_pool_t *p) { ap_register_output_filter(filter_name, case_filter, NULL, AP_FTYPE_RESOURCE); }
0x3: 運行過濾器模塊
對過濾器的配置要稍微複雜一些,在 httpd.conf 中,不但要使用 LoadModule 指令加載過濾器模塊,還要使用 SetOutputFilter 指令來指定過濾器的應用場景
LoadModule filter_echo_post_module modules/mod_filter_echo_post.so AddOutputFilter CaseFilter .cf //指令中指定,CaseFilter 這個過濾器僅對擴展名爲 .cf 的 URL 請求作過濾,其餘請求則不過濾
0x4: Code Example
/* ** mod_filter_echo_post.c -- Apache sample filter_echo_post module ** [Autogenerated via ``apxs -n filter_echo_post -g''] ** ** To play with this sample module first compile it into a ** DSO file and install it into Apache's modules directory ** by running: ** ** $ apxs -c -i mod_filter_echo_post.c ** ** Then activate it in Apache's httpd.conf file for instance ** for the URL /filter_echo_post in as follows: ** ** # httpd.conf ** LoadModule filter_echo_post_module modules/mod_filter_echo_post.so ** <Location /filter_echo_post> ** SetHandler filter_echo_post ** </Location> ** ** Then after restarting Apache via ** ** $ apachectl restart ** ** you immediately can request the URL /filter_echo_post and watch for the ** output of this module. This can be achieved for instance via: ** ** $ lynx -mime_header http://localhost/filter_echo_post ** ** The output should be similar to the following one: ** ** HTTP/1.1 200 OK ** Date: Tue, 31 Mar 1998 14:42:22 GMT ** Server: Apache/1.3.4 (Unix) ** Connection: close ** Content-Type: text/html ** ** The sample page from mod_filter_echo_post.c */ #include "httpd.h" #include "http_config.h" #include "http_request.h" #include "http_protocol.h" #include "ap_config.h" #include "apr_general.h" #include "apr_buckets.h" #include "apr_lib.h" #include "util_filter.h" static const char *filter_name = "CaseFilter"; static apr_status_t case_filter(ap_filter_t *filter, apr_bucket_brigade *bbin){ request_rec *req = filter->r; conn_rec *con = req->connection; apr_bucket *bucket; apr_bucket_brigade *bbout; bbout = apr_brigade_create(req->pool, con->bucket_alloc); APR_BRIGADE_FOREACH(bucket, bbin){ if(APR_BUCKET_IS_EOS(bucket) || APR_BUCKET_IS_FLUSH(bucket)){ APR_BUCKET_REMOVE(bucket); APR_BRIGADE_INSERT_TAIL(bbout, bucket); return ap_pass_brigade(filter->next, bbout); } char *data, *buffer; apr_size_t data_len; apr_bucket_read(bucket, &data, &data_len, APR_NONBLOCK_READ); buffer = apr_bucket_alloc(data_len, con->bucket_alloc); int i; for(i = 0; i < data_len; i++){ buffer[i] = apr_toupper(data[i]); } apr_bucket *temp_bucket; temp_bucket = apr_bucket_heap_create( buffer, data_len, apr_bucket_free, con->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bbout, temp_bucket); } return APR_SUCCESS; } /* static apr_status_t case_filter(ap_filter_t *filter, apr_bucket_brigade *bbin){ request_rec *req = filter->r; conn_rec *con = req->connection; apr_bucket *bucket; //the bucket of data apr_bucket_brigade *bbout; bbout = apr_brigade_create(req->pool, con->bucket_alloc); for(bucket = APR_BRIGADE_FIRST(bbin); bucket != APR_BRIGADE_SENTINEL(bbin); bucket = APR_BUCKET_NEXT(bucket)){ char *data, *buffer; apr_size_t data_len; apr_bucket *temp_bucket; if(APR_BUCKET_IS_EOS(bucket)){ apr_bucket *eos = apr_bucket_eos_create(con->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bbout, eos); continue; } apr_bucket_read(bucket, &data, &data_len, APR_BLOCK_READ); buffer = apr_bucket_alloc(data_len, con->bucket_alloc); int i; for(i = 0; i < data_len; i++){ buffer[i] = apr_toupper(data[i]); } temp_bucket = apr_bucket_heap_create( buffer, data_len, apr_bucket_free, con->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bbout, temp_bucket); } return ap_pass_brigade(filter->next, bbout); } */ static void filter_echo_post_register_hooks(apr_pool_t *p) { ap_register_output_filter(filter_name, case_filter, NULL, AP_FTYPE_RESOURCE); } /* Dispatch list for API hooks */ module AP_MODULE_DECLARE_DATA filter_echo_post_module = { STANDARD20_MODULE_STUFF, NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ NULL, /* table of config file commands */ filter_echo_post_register_hooks /* register hooks */ };
過濾器將該文件中的字符串轉換爲大寫字母輸出
Relevant Link:
http://www.ibm.com/developerworks/cn/opensource/os-cn-apachehttpd/ http://www.ibm.com/developerworks/cn/linux/middleware/l-apache/
Copyright (c) 2015 LittleHann All rights reserved