Nginx 模塊-細節詳探


    本文主要基於

    http://www.codinglabs.org/html/intro-of-nginx-module-development.html

    http://www.evanmiller.org/nginx-modules-guide.html#compiling

    的學習些的

     

    nginx模塊要負責三種角色

    handler:接收請求+產生Output

    filters:處理hander產生的output

    load-balancer:負載均衡,選擇一個后端server發送請求(如果把nginx當做負載均衡服務器的話,這個角色必須實現)

     


    nginx內部流程(非常重要)

    圖片講解:

    clip_image001

    英文講解

    Client sends HTTP request → Nginx chooses the appropriate handler based on the location config → (if applicable) load-balancer picks a backend server → Handler does its thing and passes each output buffer to the first filter → First filter passes the output to the second filter → second to third → third to fourth → etc. → Final response sent to client

    中文講解:

    客戶端發送http請求 -- nginx根據配置文件conf中的location來確定由哪個handler處理-- handler執行完request返回output給filter--第一個filter處理output -- 第二個filter處理output--- … -- 生成Response

     


    Nginx模塊的幾個數據結構

    1 Module Configuration Struct(s) 模塊配置結構

    2 Module Directives 模塊命令結構

    3 The Module Context模塊內容

    3.1 create_loc_conf

    3.2 merge_loc_conf

    4 The Module Definition模塊整合

    5 Module Installation模塊安裝

     

     

    1 模塊配置結構:

    這個結構的命名規則為ngx_http_[module-name]_[main|srv|loc]_conf_t。

    main,srv,loc表示這個模塊的作用范圍是配置文件中的main/server/location三種范圍(這個需要記住,后面會經常用到)

    例子:

    typedef struct {
    
    ngx_str_t ed; //echo模塊只有一個參數 比如 echo "hello"
    
    } ngx_http_echo_loc_conf_t; //echo 模塊

    2 模塊命令結構:
    例子:

    static ngx_command_t ngx_http_echo_commands[] = {
    
    { ngx_string("echo"), //命令名字
    
    NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, //代表是local配置,帶一個參數
    
    ngx_http_echo, //組裝模塊配置結構
    
    NGX_HTTP_LOC_CONF_OFFSET, //上面的組裝模塊配置獲取完參數后存放到哪里?使用這個和下面的offset參數來進行定位
    
    offsetof(ngx_http_echo_loc_conf_t, ed), //同上
    
    NULL // Finally, post is just a pointer to other crap the module might need while it's reading the configuration. It's often NULL.我也沒理解是什么意思。通常情況下設置為NULL
    
    },
    
    ngx_null_command //必須使用ngx_null_command作為commands的結束標記
    
    };

     

    注1:

    ngx_http_echo 是組裝模塊配置結構的函數指針,有三個參數:

    ngx_conf_t *cf 包含這個命令的所有參數

    ngx_command_t *cmd 執行這個command命令結構的指針

    void *conf 模塊訂制的配置結構

    這個函數比較不好理解,其功能是把參數傳到命令結構體中,並且把合適的值放入到模塊配置結構中。我們稱之為"setup function"。它會在命令運行的時候被調用。

    nginx已經提供了幾個現成的方法了:

    ngx_conf_set_flag_slot

    ngx_conf_set_str_slot

    ngx_conf_set_num_slot

    ngx_conf_set_size_slot

    所以你可以這樣定義cmd:

    { ngx_string("add_after_body"),
    
    NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
    
    ngx_conf_set_str_slot,
    
    NGX_HTTP_LOC_CONF_OFFSET,
    
    offsetof(ngx_http_addition_conf_t, after_body),
    
    NULL },
    也可以這樣:

    static ngx_command_t ngx_http_echo_commands[] = {
    
    { ngx_string("echo"),
    
    NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
    
    ngx_http_echo,
    
    NGX_HTTP_LOC_CONF_OFFSET,
    
    offsetof(ngx_http_echo_loc_conf_t, ed),
    
    NULL },
    
    ngx_null_command
    
    };
    
    static char *
    
    ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
    
    {
    
    ngx_http_core_loc_conf_t *clcf;
    
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    
    clcf->handler = ngx_http_echo_handler; //這里指定了handler,那么就會使用新的handler進行處理
    
    ngx_conf_set_str_slot(cf,cmd,conf); //這里還是使用系統的函數
    
    return NGX_CONF_OK;
    
    }

    3 模塊內容

    static ngx_http_module_t ngx_http_circle_gif_module_ctx = {

    NULL, /* preconfiguration */

    NULL, /* postconfiguration 這里是放置filter的地方,在filter章節會說*/

    NULL, /* create main configuration */

    NULL, /* init main configuration */

    NULL, /* create server configuration */

    NULL, /* merge server configuration */

    ngx_http_circle_gif_create_loc_conf, /* create location configuration */

    ngx_http_circle_gif_merge_loc_conf /* merge location configuration */

    };

    模塊的內容ngx_http_<module name>_module_ctx是為了定義各種鈎子函數,就是nginx在各個不同的時期將會運行的函數。

    一般的location只需要配置create location configuration(在創建location配置的時候運行)和merge location configuration(和server config如何合並,一般包含如果配置有錯誤的話應該拋出異常)

    這兩個函數的例子:(來自https://github.com/evanmiller/nginx_circle_gif/

    static void *

    ngx_http_circle_gif_create_loc_conf(ngx_conf_t *cf)

    {

    ngx_http_circle_gif_loc_conf_t *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_circle_gif_loc_conf_t));

    if (conf == NULL) {

    return NGX_CONF_ERROR;

    }

    conf->min_radius = NGX_CONF_UNSET_UINT; //對conf中的每個參數進行配置,min_redius和max_redius是nginx_circle_gif模塊的配置結構的字段

    conf->max_radius = NGX_CONF_UNSET_UINT;

    return conf;

    }

    static char *

    ngx_http_circle_gif_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)

    {

    ngx_http_circle_gif_loc_conf_t *prev = parent; //server的loc配置

    ngx_http_circle_gif_loc_conf_t *conf = child; // 自己的loca配置

    ngx_conf_merge_uint_value(conf->min_radius, prev->min_radius, 10);

    ngx_conf_merge_uint_value(conf->max_radius, prev->max_radius, 20);

    if (conf->min_radius < 1) {

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,

    "min_radius must be equal or more than 1");

    return NGX_CONF_ERROR; //這里負責拋出錯誤

    }

    if (conf->max_radius < conf->min_radius) {

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,

    "max_radius must be equal or more than min_radius");

    return NGX_CONF_ERROR; //這里負責拋出錯誤

    }

    return NGX_CONF_OK;

    }

    注1 : ngx_conf_merge_uint_value是nginx core中自帶的函數

    ngx_conf_merge_<data type>_value

    ngx_conf_merge_uint_value(conf->min_radius, prev->min_radius, 10);的意思是:

    如果設置了conf->min_redius的話使用conf->min_redius

    如果沒有設置conf->min_redius的話使用 prev->min_radius

    如果兩個都沒有設置的話使用10

    更多函數請看 core/ngx_conf_file.h

    4 模塊整合

    ngx_module_t ngx_http_<module name>_module = {

    NGX_MODULE_V1,

    &ngx_http_<module name>_module_ctx, /* module context 模塊內容 */

    ngx_http_<module name>_commands, /* module directives 模塊命令*/

    NGX_HTTP_MODULE, /* module type 模塊類型,HTTP模塊,或者HTTPS*/

    NULL, /* init master */

    NULL, /* init module */

    NULL, /* init process */

    NULL, /* init thread */

    NULL, /* exit thread */

    NULL, /* exit process */

    NULL, /* exit master */

    NGX_MODULE_V1_PADDING

    };

    5 模塊安裝

    模塊安裝文件的編寫依賴於這個模塊是handler,filter還是load-balancer的工作角色

    下面開始是Handler,filter,load-balancer的編寫和安裝

    1 Handler安裝

    還記得在模塊命令的時候有設置handler的語句嗎?

    clcf->handler = ngx_http_echo_handler;

    這個語句就是handler的安裝

    2 Handler編寫

    Handler的執行有四部:

    讀入模塊配置

    處理功能業務

    產生HTTP header

    產生HTTP body

    讀入模塊配置

    例子:

    static ngx_int_t
    
    ngx_http_circle_gif_handler(ngx_http_request_t *r)
    
    {
    
    ngx_http_circle_gif_loc_conf_t *circle_gif_config;
    
    circle_gif_config = ngx_http_get_module_loc_conf(r, ngx_http_circle_gif_module);
    
    ...

    就是使用nginx已經有的函數ngx_http_get_module_loc_conf,第一個參數是當前請求,第二個參數是前面寫好的模塊

    處理功能業務

    這個部分是我們要模塊處理的實際部分

    用需求舉例:

    這個模塊有個命令是 getRedisInfo 192.168.0.1 //獲取redis的信息

    那么這個功能業務就是(偽代碼):

    case cmd->opcode

    {

    "getRedisInfo" :

    獲取redis 的信息

    }

    這里應該把所有這個模塊設置的命令的業務邏輯都寫好

    產生HTTP Header

    例子:

    r->headers_out.status = NGX_HTTP_OK;

    r->headers_out.content_length_n = 100;

    r->headers_out.content_type.len = sizeof("image/gif") - 1;

    r->headers_out.content_type.data = (u_char *) "image/gif";

    ngx_http_send_header(r);

    產生HTTP Body

    這個部分是最重要的一步

    借用codingLabs的圖講解一下nginx的IO

    clip_image002

    handler是可以一次產生出一個輸出,也可以產生出多個輸出使用ngx_chain_t的鏈表來進行連接

    struct ngx_chain_s {

    ngx_buf_t *buf;

    ngx_chain_t *next;

    };

    buf中有pos和last來代表out數據在內存中的位置,next是代表下一個ngx_chain_t

    下面來說一下只有一個ngx_chain_t的設置

    1 申明

    ngx_buf_t *b;

    ngx_chain_t out

    2 設置buffer

    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));

    if (b == NULL) {

    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,

    "Failed to allocate response buffer.");

    return NGX_HTTP_INTERNAL_SERVER_ERROR;

    }

    b->pos = some_bytes; /* first position in memory of the data */

    b->last = some_bytes + some_bytes_length; /* last position */

    b->memory = 1; /* content is in read-only memory */

    /* (i.e., filters should copy it rather than rewrite in place) */

    b->last_buf = 1; /* there will be no more buffers in the request */

    3 模塊加入鏈表

    out.buf = b;

    out.next = NULL; //如果有下一個鏈表可以放到這里

    4 返回

    return ngx_http_output_filter(r, &out);

     

     

    2 Filter的編寫和安裝

    Filter作為過濾器又可以細分為兩個過濾器: Header filters 和 body filters

     

    Filter的安裝

    filter是在模塊內容設置的時候加上的

    例子:

    static ngx_http_module_t ngx_http_chunked_filter_module_ctx = {

    NULL, /* preconfiguration */

    ngx_http_chunked_filter_init, /* postconfiguration */

    ...

    };

    static ngx_int_t

    ngx_http_chunked_filter_init(ngx_conf_t *cf)

    {

    ngx_http_next_header_filter = ngx_http_top_header_filter;

    ngx_http_top_header_filter = ngx_http_chunked_header_filter;

    ngx_http_next_body_filter = ngx_http_top_body_filter;

    ngx_http_top_body_filter = ngx_http_chunked_body_filter;

    return NGX_OK;

    }

    注1: ngx_http_top_hreader_filter是什么意思呢?

    當handler生成了response的時候,它調用了兩個方法:ngx_http_output_filter和ngx_http_send_header

    ngx_http_output_filter會調用ngx_http_top_body_filter

    ngx_http_send_header會調用ngx_top_header_filter

    Filter的編寫

    Header filters

    分為三個部分:

    是否操作這個handler的response

    操作response

    調用下一個filter

    例子

    static
    
    ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r)
    
    {
    
    time_t if_modified_since;
    
    if_modified_since = ngx_http_parse_time(r->headers_in.if_modified_since->value.data,
    
    r->headers_in.if_modified_since->value.len);
    
    /* step 1: decide whether to operate */
    
    if (if_modified_since != NGX_ERROR && 
    
    if_modified_since == r->headers_out.last_modified_time) {
    
    /* step 2: operate on the header */
    
    r->headers_out.status = NGX_HTTP_NOT_MODIFIED; //返回304
    
    r->headers_out.content_type.len = 0; //長度設置為0
    
    ngx_http_clear_content_length(r); //清空
    
    ngx_http_clear_accept_ranges(r); //清空
    
    }
    
    /* step 3: call the next filter */
    
    return ngx_http_next_header_filter(r);
    
    }

    Body filters

    假設有個需求:在每個request后面插入"<l!-- Served by Nginx -->"

    1 要找出最后chain的最后一個buf

    ngx_chain_t *chain_link;
    
    int chain_contains_last_buffer = 0;
    
    for ( chain_link = in; chain_link != NULL; chain_link = chain_link->next ) {
    
    if (chain_link->buf->last_buf)
    
    chain_contains_last_buffer = 1;
    
    }

    2 創建一個新的buf

    ngx_buf_t *b;
    
    b = ngx_calloc_buf(r->pool);
    
    if (b == NULL) {
    
    return NGX_ERROR;
    
    }
    3 放數據在新buf上

    b->pos = (u_char *) "<!-- Served by Nginx -->";
    
    b->last = b->pos + sizeof("<!-- Served by Nginx -->") - 1;
    4 把新buf放入一個新chain_t

    ngx_chain_t *added_link;
    
    added_link = ngx_alloc_chain_link(r->pool);
    
    if (added_link == NULL)
    
    return NGX_ERROR;
    
    added_link->buf = b;
    
    added_link->next = NULL;
    5 把新的chain鏈接到原來的chain_link中

    chain_link->next = added_link;

    6 重新設置last_buf

    chain_link->buf->last_buf = 0;

    added_link->buf->last_buf = 1;

    7 傳給下一個filter

    return ngx_http_next_body_filter(r, in);

     

    最后一點是如何寫和編譯nginx模塊

    必須寫兩個文件configngx_http_<your module>_module.c

    其中config會被./configure包含

    ngx_addon_name=ngx_http_<your module>_module

    HTTP_MODULES="$HTTP_MODULES ngx_http_<your module>_module"

    NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_<your module>_module.c"

    幾乎都是填空

    ngx_http_<your module>_module.c文件就是你的所有模塊代碼

     

    編譯nginx:

    ./configure --add-module=path/to/your/new/module/directory #這里是config放置的地方

 

    ----------------------

    作者:yjf512(軒脈刃)

    出處:http://www.cnblogs.com/yjf512/

    本文版權歸yjf512和cnBlog共有,歡迎轉載,但未經作者同意必須保留此段聲明


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM