鍍金池/ 教程/ Linux/ 更多 handler 模塊示例分析
示例: hello handler 模塊
什么是 Nginx
handler 模塊的掛載
Nginx 特點(diǎn)
handler 模塊簡(jiǎn)介
初探 Nginx 架構(gòu)
Nginx 的模塊化體系結(jié)構(gòu)
更多 handler 模塊示例分析
Nginx 基礎(chǔ)概念
upstream 模塊簡(jiǎn)介
Nginx 的請(qǐng)求處理
過(guò)濾模塊簡(jiǎn)介
基本數(shù)據(jù)結(jié)構(gòu)
模塊的基本結(jié)構(gòu)
負(fù)載均衡模塊
過(guò)濾模塊的分析
core 模塊
handler 模塊的基本結(jié)構(gòu)
Nginx 的配置系統(tǒng)
handler 的編寫(xiě)步驟
handler 模塊的編譯和使用
event 模塊

更多 handler 模塊示例分析

http access module

該模塊的代碼位于src/http/modules/ngx_http_access_module.c中。該模塊的作用是提供對(duì)于特定 host 的客戶端的訪問(wèn)控制??梢韵薅ㄌ囟?host 的客戶端對(duì)于服務(wù)端全部,或者某個(gè) server,或者是某個(gè) location 的訪問(wèn)。

該模塊的實(shí)現(xiàn)非常簡(jiǎn)單,總共也就只有幾個(gè)函數(shù)。

    static ngx_int_t ngx_http_access_handler(ngx_http_request_t *r);
    static ngx_int_t ngx_http_access_inet(ngx_http_request_t *r,
        ngx_http_access_loc_conf_t *alcf, in_addr_t addr);
    #if (NGX_HAVE_INET6)
    static ngx_int_t ngx_http_access_inet6(ngx_http_request_t *r,
        ngx_http_access_loc_conf_t *alcf, u_char *p);
    #endif
    static ngx_int_t ngx_http_access_found(ngx_http_request_t *r, ngx_uint_t deny);
    static char *ngx_http_access_rule(ngx_conf_t *cf, ngx_command_t *cmd,
        void *conf);
    static void *ngx_http_access_create_loc_conf(ngx_conf_t *cf);
    static char *ngx_http_access_merge_loc_conf(ngx_conf_t *cf,
        void *parent, void *child);
    static ngx_int_t ngx_http_access_init(ngx_conf_t *cf);

對(duì)于與配置相關(guān)的幾個(gè)函數(shù)都不需要做解釋了,需要提一下的是函數(shù) ngx_http_access_init,該函數(shù)在實(shí)現(xiàn)上把本模塊掛載到了 NGX_HTTP_ACCESS_PHASE 階段的 handler 上,從而使自己的被調(diào)用時(shí)機(jī)發(fā)生在了 NGX_HTTP_CONTENT_PHASE 等階段前。因?yàn)檫M(jìn)行客戶端地址的限制檢查,根本不需要等到這么后面。

另外看一下這個(gè)模塊的主處理函數(shù) ngx_http_access_handler。這個(gè)函數(shù)的邏輯也非常簡(jiǎn)單,主要是根據(jù)客戶端地址的類型,來(lái)分別選擇 ipv4 類型的處理函數(shù) ngx_http_access_inet 還是 ipv6 類型的處理函數(shù) ngx_http_access_inet6。

而這個(gè)兩個(gè)處理函數(shù)內(nèi)部也非常簡(jiǎn)單,就是循環(huán)檢查每個(gè)規(guī)則,檢查是否有匹配的規(guī)則,如果有就返回匹配的結(jié)果,如果都沒(méi)有匹配,就默認(rèn)拒絕。

http static module

從某種程度上來(lái)說(shuō),此模塊可以算的上是“最正宗的”,“最古老”的 content handler。因?yàn)楸灸K的作用就是讀取磁盤(pán)上的靜態(tài)文件,并把文件內(nèi)容作為產(chǎn)生的輸出。在Web技術(shù)發(fā)展的早期,只有靜態(tài)頁(yè)面,沒(méi)有服務(wù)端腳本來(lái)動(dòng)態(tài)生成 HTML 的時(shí)候??峙麻_(kāi)發(fā)個(gè) Web 服務(wù)器的時(shí)候,第一個(gè)要開(kāi)發(fā)就是這樣一個(gè) content handler。

http static module 的代碼位于src/http/modules/ngx_http_static_module.c中,總共只有兩百多行近三百行??梢哉f(shuō)是非常短小。

我們首先來(lái)看一下該模塊的模塊上下文的定義。

    ngx_http_module_t  ngx_http_static_module_ctx = {
        NULL,                                  /* preconfiguration */
        ngx_http_static_init,                  /* postconfiguration */

        NULL,                                  /* create main configuration */
        NULL,                                  /* init main configuration */

        NULL,                                  /* create server configuration */
        NULL,                                  /* merge server configuration */

        NULL,                                  /* create location configuration */
        NULL                                   /* merge location configuration */
    };

是非常的簡(jiǎn)潔吧,連任何與配置相關(guān)的函數(shù)都沒(méi)有。對(duì)了,因?yàn)樵撃K沒(méi)有提供任何配置指令。大家想想也就知道了,這個(gè)模塊做的事情實(shí)在是太簡(jiǎn)單了,也確實(shí)沒(méi)什么好配置的。唯一需要調(diào)用的函數(shù)是一個(gè) ngx_http_static_init 函數(shù)。好了,來(lái)看一下這個(gè)函數(shù)都干了寫(xiě)什么。

    static ngx_int_t
    ngx_http_static_init(ngx_conf_t *cf)
    {
        ngx_http_handler_pt        *h;
        ngx_http_core_main_conf_t  *cmcf;

        cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

        h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
        if (h == NULL) {
            return NGX_ERROR;
        }

        *h = ngx_http_static_handler;

        return NGX_OK;
    }

僅僅是掛載這個(gè) handler 到 NGX_HTTP_CONTENT_PHASE 處理階段。簡(jiǎn)單吧?

下面我們就看一下這個(gè)模塊最核心的處理邏輯所在的 ngx_http_static_handler 函數(shù)。該函數(shù)大概占了這個(gè)模塊代碼量的百分之八九十。

    static ngx_int_t
    ngx_http_static_handler(ngx_http_request_t *r)
    {
        u_char                    *last, *location;
        size_t                     root, len;
        ngx_str_t                  path;
        ngx_int_t                  rc;
        ngx_uint_t                 level;
        ngx_log_t                 *log;
        ngx_buf_t                 *b;
        ngx_chain_t                out;
        ngx_open_file_info_t       of;
        ngx_http_core_loc_conf_t  *clcf;

        if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
            return NGX_HTTP_NOT_ALLOWED;
        }

        if (r->uri.data[r->uri.len - 1] == '/') {
            return NGX_DECLINED;
        }

        log = r->connection->log;

        /*
         * ngx_http_map_uri_to_path() allocates memory for terminating '\0'
         * so we do not need to reserve memory for '/' for possible redirect
         */

        last = ngx_http_map_uri_to_path(r, &path, &root, 0);
        if (last == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        path.len = last - path.data;

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
                       "http filename: \"%s\"", path.data);

        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

        ngx_memzero(&of, sizeof(ngx_open_file_info_t));

        of.read_ahead = clcf->read_ahead;
        of.directio = clcf->directio;
        of.valid = clcf->open_file_cache_valid;
        of.min_uses = clcf->open_file_cache_min_uses;
        of.errors = clcf->open_file_cache_errors;
        of.events = clcf->open_file_cache_events;

        if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
            != NGX_OK)
        {
            switch (of.err) {

            case 0:
                return NGX_HTTP_INTERNAL_SERVER_ERROR;

            case NGX_ENOENT:
            case NGX_ENOTDIR:
            case NGX_ENAMETOOLONG:

                level = NGX_LOG_ERR;
                rc = NGX_HTTP_NOT_FOUND;
                break;

            case NGX_EACCES:
    #if (NGX_HAVE_OPENAT)
            case NGX_EMLINK:
            case NGX_ELOOP:
    #endif

                level = NGX_LOG_ERR;
                rc = NGX_HTTP_FORBIDDEN;
                break;

            default:

                level = NGX_LOG_CRIT;
                rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
                break;
            }

            if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
                ngx_log_error(level, log, of.err,
                              "%s \"%s\" failed", of.failed, path.data);
            }

            return rc;
        }

        r->root_tested = !r->error_page;

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd);

        if (of.is_dir) {

            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");

            ngx_http_clear_location(r);

            r->headers_out.location = ngx_palloc(r->pool, sizeof(ngx_table_elt_t));
            if (r->headers_out.location == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            len = r->uri.len + 1;

            if (!clcf->alias && clcf->root_lengths == NULL && r->args.len == 0) {
                location = path.data + clcf->root.len;

                *last = '/';

            } else {
                if (r->args.len) {
                    len += r->args.len + 1;
                }

                location = ngx_pnalloc(r->pool, len);
                if (location == NULL) {
                    return NGX_HTTP_INTERNAL_SERVER_ERROR;
                }

                last = ngx_copy(location, r->uri.data, r->uri.len);

                *last = '/';

                if (r->args.len) {
                    *++last = '?';
                    ngx_memcpy(++last, r->args.data, r->args.len);
                }
            }

            /*
             * we do not need to set the r->headers_out.location->hash and
             * r->headers_out.location->key fields
             */

            r->headers_out.location->value.len = len;
            r->headers_out.location->value.data = location;

            return NGX_HTTP_MOVED_PERMANENTLY;
        }

    #if !(NGX_WIN32) /* the not regular files are probably Unix specific */

        if (!of.is_file) {
            ngx_log_error(NGX_LOG_CRIT, log, 0,
                          "\"%s\" is not a regular file", path.data);

            return NGX_HTTP_NOT_FOUND;
        }

    #endif

        if (r->method & NGX_HTTP_POST) {
            return NGX_HTTP_NOT_ALLOWED;
        }

        rc = ngx_http_discard_request_body(r);

        if (rc != NGX_OK) {
            return rc;
        }

        log->action = "sending response to client";

        r->headers_out.status = NGX_HTTP_OK;
        r->headers_out.content_length_n = of.size;
        r->headers_out.last_modified_time = of.mtime;

        if (ngx_http_set_content_type(r) != NGX_OK) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        if (r != r->main && of.size == 0) {
            return ngx_http_send_header(r);
        }

        r->allow_ranges = 1;

        /* we need to allocate all before the header would be sent */

        b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
        if (b == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
        if (b->file == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        rc = ngx_http_send_header(r);

        if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
            return rc;
        }

        b->file_pos = 0;
        b->file_last = of.size;

        b->in_file = b->file_last ? 1: 0;
        b->last_buf = (r == r->main) ? 1: 0;
        b->last_in_chain = 1;

        b->file->fd = of.fd;
        b->file->name = path;
        b->file->log = log;
        b->file->directio = of.is_directio;

        out.buf = b;
        out.next = NULL;

        return ngx_http_output_filter(r, &out);
    }

首先是檢查客戶端的 http 請(qǐng)求類型(r->method),如果請(qǐng)求類型為NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST,則繼續(xù)進(jìn)行處理,否則一律返回 NGX_HTTP_NOT_ALLOWED 從而拒絕客戶端的發(fā)起的請(qǐng)求。

其次是檢查請(qǐng)求的 url 的結(jié)尾字符是不是斜杠/,如果是說(shuō)明請(qǐng)求的不是一個(gè)文件,給后續(xù)的 handler 去處理,比如后續(xù)的 ngx_http_autoindex_handler(如果是請(qǐng)求的是一個(gè)目錄下面,可以列出這個(gè)目錄的文件),或者是 ngx_http_index_handler(如果請(qǐng)求的路徑下面有個(gè)默認(rèn)的 index 文件,直接返回 index 文件的內(nèi)容)。

然后接下來(lái)調(diào)用了一個(gè) ngx_http_map_uri_to_path 函數(shù),該函數(shù)的作用是把請(qǐng)求的 http 協(xié)議的路徑轉(zhuǎn)化成一個(gè)文件系統(tǒng)的路徑。

然后根據(jù)轉(zhuǎn)化出來(lái)的具體路徑,去打開(kāi)文件,打開(kāi)文件的時(shí)候做了 2 種檢查,一種是,如果請(qǐng)求的文件是個(gè) symbol link,根據(jù)配置,是否允許符號(hào)鏈接,不允許返回錯(cuò)誤。還有一個(gè)檢查是,如果請(qǐng)求的是一個(gè)名稱,是一個(gè)目錄的名字,也返回錯(cuò)誤。如果都沒(méi)有錯(cuò)誤,就讀取文件,返回內(nèi)容。其實(shí)說(shuō)返回內(nèi)容可能不是特別準(zhǔn)確,比較準(zhǔn)確的說(shuō)法是,把產(chǎn)生的內(nèi)容傳遞給后續(xù)的 filter 去處理。

http log module

該模塊提供了對(duì)于每一個(gè) http 請(qǐng)求進(jìn)行記錄的功能,也就是我們見(jiàn)到的 access.log。當(dāng)然這個(gè)模塊對(duì)于 log 提供了一些配置指令,使得可以比較方便的定制 access.log。

這個(gè)模塊的代碼位于src/http/modules/ngx_http_log_module.c,雖然這個(gè)模塊的代碼有接近 1400 行,但是主要的邏輯在于對(duì)日志本身格式啊,等細(xì)節(jié)的處理。我們?cè)谶@里進(jìn)行分析主要是關(guān)注,如何編寫(xiě)一個(gè) log handler 的問(wèn)題。

由于 log handler 的時(shí)候,拿到的參數(shù)也是 request 這個(gè)東西,那么也就意味著我們?nèi)绻枰?,可以好好研究下這個(gè)結(jié)構(gòu),把我們需要的所有信息都記錄下來(lái)。

對(duì)于 log handler,有一點(diǎn)特別需要注意的就是,log handler 是無(wú)論如何都會(huì)被調(diào)用的,就是只要服務(wù)端接受到了一個(gè)客戶端的請(qǐng)求,也就是產(chǎn)生了一個(gè) request 對(duì)象,那么這些個(gè) log handler 的處理函數(shù)都會(huì)被調(diào)用的,就是在釋放 request 的時(shí)候被調(diào)用的(ngx_http_free_request函數(shù))。

那么當(dāng)然絕對(duì)不能忘記的就是 log handler 最好,也是建議被掛載在 NGX_HTTP_LOG_PHASE 階段。因?yàn)閽燧d在其他階段,有可能在某些情況下被跳過(guò),而沒(méi)有執(zhí)行到,導(dǎo)致你的 log 模塊記錄的信息不全。

還有一點(diǎn)要說(shuō)明的是,由于 Nginx 是允許在某個(gè)階段有多個(gè) handler 模塊存在的,根據(jù)其處理結(jié)果,確定是否要調(diào)用下一個(gè) handler。但是對(duì)于掛載在 NGX_HTTP_LOG_PHASE 階段的 handler,則根本不關(guān)注這里 handler 的具體處理函數(shù)的返回值,所有的都被調(diào)用。如下,位于src/http/ngx_http_request.c中的 ngx_http_log_request 函數(shù)。

    static void
    ngx_http_log_request(ngx_http_request_t *r)
    {
        ngx_uint_t                  i, n;
        ngx_http_handler_pt        *log_handler;
        ngx_http_core_main_conf_t  *cmcf;

        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

        log_handler = cmcf->phases[NGX_HTTP_LOG_PHASE].handlers.elts;
        n = cmcf->phases[NGX_HTTP_LOG_PHASE].handlers.nelts;

        for (i = 0; i < n; i++) {
            log_handler[i](r);
        }
    }