源碼以前,了無祕密!
1、知其然
開發一個Nginx模塊,將本身的HTTP模塊編譯進Nginx
三步走:
1.編寫實際開發的xxx.c文件
2.編寫的xxx.c文件目錄下編寫shell腳本的config文件,開發一個HTTP模塊,config文件中必要的3個變量以下:
ngx_addon_name:模塊名稱,只在configure執行時使用
HTTP_MODULES:保存全部的HTTP模塊名稱,變量的賦值定義方式--->var = "var xxx_module" 例如 HTTP_MODULES="HTTP_MODULES ndg_test_module" 至關於把新的字符串加到變量末尾,相似於「+=」操做符
NDG_ADDON_SRCS:指定模塊的源代碼
3.編譯命令 ./configure --add-module=$your_module_addr
代碼來自《Nginx徹底開發指南》---羅劍鋒
NdgTestModule.cpp
配置的數據結構node
struct NdgTestConf final { ngx_flag_t enabled = NgxUnsetValue::get(); //ngx_flag_t類型 將配置文件nginx.conf中的location中的on|off信息轉換成1|0 };
配置的解析nginx
static ngx_command_t ndg_test_cmds[] = //配置指令數組 { { ngx_string("ndg_test"), //指令的名字 NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, //指令的做用域和類型 ngx_conf_set_flag_slot, //解析函數指針 NGX_HTTP_LOC_CONF_OFFSET, //數據的存儲位置 offsetof(NdgTestConf, enabled), nullptr }, ngx_null_command //空對象,數組結束 };
建立配置數據c++
static void* create(ngx_conf_t* cf) { return NgxPool(cf).alloc<NdgTestConf>(); }
處理函數shell
static ngx_int_t handler(ngx_http_request_t *r) { auto cf = reinterpret_cast<NdgTestConf*>( ngx_http_get_module_loc_conf(r, ndg_test_module)); NgxLogError(r).print("hello c++"); if (cf->enabled) { std::cout << "hello nginx" << std::endl; } else { std::cout << "hello disabled" << std::endl; } return NGX_DECLINED; }
註冊處理函數(init)數組
static ngx_int_t init(ngx_conf_t* cf) { auto cmcf = reinterpret_cast<ngx_http_core_main_conf_t*>( ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module)); NgxArray<ngx_http_handler_pt> arr( cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers); arr.push(handler); return NGX_OK; }
集成配置函數(ngx_http_module_t)數據結構
static ngx_http_module_t ndg_test_ctx = { nullptr, init, nullptr, nullptr, nullptr, nullptr, create, nullptr, };
集成配置指令(ngx_module_t)架構
ngx_module_t ndg_test_module = { NGX_MODULE_V1, // 標準的填充宏 &ndg_test_ctx, // 配置功能函數 ndg_test_cmds, // 配置指令數組 NGX_HTTP_MODULE, // type nullptr, // init master nullptr, // init module nullptr, // init process nullptr, // init thread nullptr, // exit thread nullptr, // exit process nullptr, // exit master NGX_MODULE_V1_PADDING // 標準的填充宏 };
config示例框架
ngx_addon_name=ndg_test_module ngx_module_type=HTTP ngx_module_name=ndg_test_module ngx_module_srcs="$ngx_addon_dir/NdgTestModule.cpp" HTTP_MODULES="$HTTP_MODULES ndg_test_module" NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/NdgTestModule.cpp"
2、因此然函數
先認識幾個模塊的結構post
typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s { ngx_uint_t ctx_index; //type類模塊數組裏的序號(二級索引) ngx_uint_t index; //全部模塊數組裏的序號(一級索引) char *name; //模塊的名字 ngx_uint_t spare0; //保留字段 ngx_uint_t spare1; ngx_uint_t version; //模塊版本,值爲nginx_version const char *signature; //模塊簽名,特徵碼 void *ctx; //函數指針表 ngx_command_t *commands; //模塊指令數組 ngx_uint_t type; //模塊類型標記 ngx_int_t (*init_master)(ngx_log_t *log); //進程、線程初始化和退出時的回調函數指針(7個) ngx_int_t (*init_module)(ngx_cycle_t *cycle); ngx_int_t (*init_process)(ngx_cycle_t *cycle); ngx_int_t (*init_thread)(ngx_cycle_t *cycle); void (*exit_thread)(ngx_cycle_t *cycle); void (*exit_process)(ngx_cycle_t *cycle); void (*exit_master)(ngx_cycle_t *cycle); uintptr_t spare_hook0; //保留字段(8個) uintptr_t spare_hook1; uintptr_t spare_hook2; uintptr_t spare_hook3; uintptr_t spare_hook4; uintptr_t spare_hook5; uintptr_t spare_hook6; uintptr_t spare_hook7; };
Nginx中的填充宏使上述定義簡化爲
struct ngx_module_s { NGX_MODULE_V1,void *ctx; ngx_command_t *commands; ngx_uint_t type; NULL, ...(共7個NULL),
NGX_MODULE_V1_PADDING };
ctx是函數指針表,相似C++虛繼承裏面的虛函數表,Nginx的6類模塊定義了本身的ctx結構,命名爲ngx_xxx_module_t
typedef struct { ngx_str_t name; void *(*create_conf)(ngx_cycle_t *cycle); char *(*init_conf)(ngx_cycle_t *cycle, void *conf); } ngx_core_module_t;
typedef struct { ngx_str_t name; void *(*create_conf)(ngx_cycle_t *cycle); char *(*init_conf)(ngx_cycle_t *cycle, void *conf); } ngx_core_module_t;
typedef struct { ngx_int_t (*preconfiguration)(ngx_conf_t *cf); ngx_int_t (*postconfiguration)(ngx_conf_t *cf); void *(*create_main_conf)(ngx_conf_t *cf); char *(*init_main_conf)(ngx_conf_t *cf, void *conf); void *(*create_srv_conf)(ngx_conf_t *cf); char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf); void *(*create_loc_conf)(ngx_conf_t *cf); char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); } ngx_http_module_t;
typedef struct { void **main_conf; void **srv_conf; void **loc_conf; } ngx_http_conf_ctx_t;
typedef struct ngx_command_s ngx_command_t; struct ngx_command_s { ngx_str_t name; //指令的名字 ngx_uint_t type; //指令的做用域和類型 char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); //指令解析函數 ngx_uint_t conf; //配置結構所在的存儲位置 ngx_uint_t offset; //具體的存儲變量的偏移量 void *post; };
上面單獨把一些結構拎出來,接下來以ngx_core_module爲引粗淺介紹模塊的配置解析
ngx_module_t定義了不少Nginx框架在加載模塊時所必需的信息,能夠在編譯生成的objs/ngx_modules.c文件種看到所加載的模塊信息
部分截圖以下:
後面會對應模塊的名字:
在nginx.c文件(main函數也在nginx.c)中
ngx_module_t ngx_core_module = { NGX_MODULE_V1, &ngx_core_module_ctx, /* module context */ ngx_core_commands, /* module directives */ NGX_CORE_MODULE, /* module type */ 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 };
對着上文提到的ngx_module_t的結構來看會比較清晰,NGX_MODULE_V1和NGX_MODULE_V1_PADDING是標準版填充宏。初始化ngx_core_module時調用ngx_core_module_ctx(void* 函數指針有點c++中的泛型和虛函數表的意思),接着調用ngx_core_commands指令數組,NGX_CORE_MODULE是類型標記的type。
Nginx的6類模塊(core,conf,event,stream,http,mail)定義了本身ctx結構(函數指針表),命令規範爲ngx_xxx_module_t
static ngx_core_module_t ngx_core_module_ctx = { ngx_string("core"), //模塊名字 ngx_core_module_create_conf, ngx_core_module_init_conf };
類比於第一部分中的ngx_http_module_t結構,模塊的ctx函數指針表,接下來看core模塊的create分配內存建立配置數據結構
static void * ngx_core_module_create_conf(ngx_cycle_t *cycle) { ngx_core_conf_t *ccf; //配置結構的數據指針 ccf = ngx_pcalloc(cycle->pool, sizeof(ngx_core_conf_t)); //建立配置數據結構 if (ccf == NULL) { return NULL; } /* * set by ngx_pcalloc() * * ccf->pid = NULL; * ccf->oldpid = NULL; * ccf->priority = 0; * ccf->cpu_affinity_auto = 0; * ccf->cpu_affinity_n = 0; * ccf->cpu_affinity = NULL; */ ccf->daemon = NGX_CONF_UNSET; //值都置爲UNSET ccf->master = NGX_CONF_UNSET; ccf->timer_resolution = NGX_CONF_UNSET_MSEC; ccf->shutdown_timeout = NGX_CONF_UNSET_MSEC; ccf->worker_processes = NGX_CONF_UNSET; ccf->debug_points = NGX_CONF_UNSET; ccf->rlimit_nofile = NGX_CONF_UNSET; ccf->rlimit_core = NGX_CONF_UNSET; ccf->user = (ngx_uid_t) NGX_CONF_UNSET_UINT; ccf->group = (ngx_gid_t) NGX_CONF_UNSET_UINT; if (ngx_array_init(&ccf->env, cycle->pool, 1, sizeof(ngx_str_t)) != NGX_OK) { return NULL; } return ccf; }
以上這一步類比於第一部分的create函數。
ngx_core_conf_t是core模塊須要配置的數據結構,保存了Nginx運行所需的基本參數,結構以下:
typedef struct { ngx_flag_t daemon; //守護進程標誌位 ngx_flag_t master; //master進程標誌位 ngx_msec_t timer_resolution; ngx_msec_t shutdown_timeout; ngx_int_t worker_processes; ngx_int_t debug_points; ngx_int_t rlimit_nofile; off_t rlimit_core; int priority; ngx_uint_t cpu_affinity_auto; ngx_uint_t cpu_affinity_n; ngx_cpuset_t *cpu_affinity; char *username; ngx_uid_t user; ngx_gid_t group; ngx_str_t working_directory; ngx_str_t lock_file; ngx_str_t pid; ngx_str_t oldpid; ngx_array_t env; char **environment; ngx_uint_t transparent; /* unsigned transparent:1; */ } ngx_core_conf_t;
建立好了ngx_core_conf_t數據結構,接下來是ngx_core_module_init_conf函數初始化配置結構
static char * ngx_core_module_init_conf(ngx_cycle_t *cycle, void *conf) { ngx_core_conf_t *ccf = conf; ngx_conf_init_value(ccf->daemon, 1); //默認啓用守護進程,#define ngx_conf_init_value Nginx以宏的形式提供初始化和條件賦值 ngx_conf_init_value(ccf->master, 1); ngx_conf_init_msec_value(ccf->timer_resolution, 0); ngx_conf_init_msec_value(ccf->shutdown_timeout, 0); ngx_conf_init_value(ccf->worker_processes, 1); ngx_conf_init_value(ccf->debug_points, 0); #if (NGX_HAVE_CPU_AFFINITY) if (!ccf->cpu_affinity_auto && ccf->cpu_affinity_n && ccf->cpu_affinity_n != 1 && ccf->cpu_affinity_n != (ngx_uint_t) ccf->worker_processes) { ngx_log_error(NGX_LOG_WARN, cycle->log, 0, "the number of \"worker_processes\" is not equal to " "the number of \"worker_cpu_affinity\" masks, " "using last mask for remaining worker processes"); } #endif if (ccf->pid.len == 0) { ngx_str_set(&ccf->pid, NGX_PID_PATH); } if (ngx_conf_full_name(cycle, &ccf->pid, 0) != NGX_OK) { return NGX_CONF_ERROR; } ccf->oldpid.len = ccf->pid.len + sizeof(NGX_OLDPID_EXT); ccf->oldpid.data = ngx_pnalloc(cycle->pool, ccf->oldpid.len); if (ccf->oldpid.data == NULL) { return NGX_CONF_ERROR; } ngx_memcpy(ngx_cpymem(ccf->oldpid.data, ccf->pid.data, ccf->pid.len), NGX_OLDPID_EXT, sizeof(NGX_OLDPID_EXT)); #if !(NGX_WIN32) if (ccf->user == (uid_t) NGX_CONF_UNSET_UINT && geteuid() == 0) { struct group *grp; struct passwd *pwd; ngx_set_errno(0); pwd = getpwnam(NGX_USER); if (pwd == NULL) { ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, "getpwnam(\"" NGX_USER "\") failed"); return NGX_CONF_ERROR; } ccf->username = NGX_USER; ccf->user = pwd->pw_uid; ngx_set_errno(0); grp = getgrnam(NGX_GROUP); if (grp == NULL) { ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, "getgrnam(\"" NGX_GROUP "\") failed"); return NGX_CONF_ERROR; } ccf->group = grp->gr_gid; } if (ccf->lock_file.len == 0) { ngx_str_set(&ccf->lock_file, NGX_LOCK_PATH); } if (ngx_conf_full_name(cycle, &ccf->lock_file, 0) != NGX_OK) { return NGX_CONF_ERROR; } { ngx_str_t lock_file; lock_file = cycle->old_cycle->lock_file; if (lock_file.len) { lock_file.len--; if (ccf->lock_file.len != lock_file.len || ngx_strncmp(ccf->lock_file.data, lock_file.data, lock_file.len) != 0) { ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "\"lock_file\" could not be changed, ignored"); } cycle->lock_file.len = lock_file.len + 1; lock_file.len += sizeof(".accept"); cycle->lock_file.data = ngx_pstrdup(cycle->pool, &lock_file); if (cycle->lock_file.data == NULL) { return NGX_CONF_ERROR; } } else { cycle->lock_file.len = ccf->lock_file.len + 1; cycle->lock_file.data = ngx_pnalloc(cycle->pool, ccf->lock_file.len + sizeof(".accept")); if (cycle->lock_file.data == NULL) { return NGX_CONF_ERROR; } ngx_memcpy(ngx_cpymem(cycle->lock_file.data, ccf->lock_file.data, ccf->lock_file.len), ".accept", sizeof(".accept")); } } #endif return NGX_CONF_OK; }
接下來繼續回到ngx_module_t ngx_core_module = {}; 中調用ngx_core_commands指令數組,以ngx_null_command結束數組
static ngx_command_t ngx_core_commands[] = { { ngx_string("daemon"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, 0, offsetof(ngx_core_conf_t, daemon), NULL }, { ngx_string("master_process"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, 0, offsetof(ngx_core_conf_t, master), NULL }, { ngx_string("timer_resolution"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, 0, offsetof(ngx_core_conf_t, timer_resolution), NULL }, { ngx_string("pid"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, 0, offsetof(ngx_core_conf_t, pid), NULL }, { ngx_string("lock_file"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, 0, offsetof(ngx_core_conf_t, lock_file), NULL }, { ngx_string("worker_processes"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_set_worker_processes, 0, 0, NULL }, { ngx_string("debug_points"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_conf_set_enum_slot, 0, offsetof(ngx_core_conf_t, debug_points), &ngx_debug_points }, { ngx_string("user"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE12, ngx_set_user, 0, 0, NULL }, { ngx_string("worker_priority"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_set_priority, 0, 0, NULL }, { ngx_string("worker_cpu_affinity"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_1MORE, ngx_set_cpu_affinity, 0, 0, NULL }, { ngx_string("worker_rlimit_nofile"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, 0, offsetof(ngx_core_conf_t, rlimit_nofile), NULL }, { ngx_string("worker_rlimit_core"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_conf_set_off_slot, 0, offsetof(ngx_core_conf_t, rlimit_core), NULL }, { ngx_string("worker_shutdown_timeout"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, 0, offsetof(ngx_core_conf_t, shutdown_timeout), NULL }, { ngx_string("working_directory"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, 0, offsetof(ngx_core_conf_t, working_directory), NULL }, { ngx_string("env"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_set_env, 0, 0, NULL }, { ngx_string("load_module"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_load_module, 0, 0, NULL }, ngx_null_command };
在ngx_core_command[]中,每個數組元素結構體中有指令的名字,指令的做用域。例如NGX_HTTP_MAIN_CONF表示指令能夠出如今配置文件(nginx.conf)的http塊裏面,NGX_HTTP_LOC_CONF表示能夠出如今location塊裏面。NGX_MAIN_CONF|NGX_DIRECT_CONF表示只能出如今配置文件最外層的main域,不是http main域。根據ngx_command_s結構體裏面的conf和offset在配置的數據結構ngx_core_conf_t裏找到變量位置,經過ngx_conf_set_flag_slot之類的解析函數來進行指令的解析。以ngx_conf_set_flag_slot爲例將 on|off 解析成數字 1|0
char * ngx_conf_set_flag_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *p = conf; ngx_str_t *value; ngx_flag_t *fp; ngx_conf_post_t *post; fp = (ngx_flag_t *) (p + cmd->offset); if (*fp != NGX_CONF_UNSET) { return "is duplicate"; } value = cf->args->elts; if (ngx_strcasecmp(value[1].data, (u_char *) "on") == 0) { *fp = 1; } else if (ngx_strcasecmp(value[1].data, (u_char *) "off") == 0) { *fp = 0; } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value \"%s\" in \"%s\" directive, " "it must be \"on\" or \"off\"", value[1].data, cmd->name.data); return NGX_CONF_ERROR; } if (cmd->post) { post = cmd->post; return post->post_handler(cf, post, fp); } return NGX_CONF_OK; }
ngx_core_module自己沒有業務邏輯,只是提供了Nginx運行所需的基本參數,由Nginx框架在運行時直接獲取和使用。
啓動Nginx時,nginx.c文件中的main函數調用ngx_get_conf來從conf_ctx數組裏獲取配置結構,對於core模塊來講就是ngx_core_conf_t
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
conf_ctx定義在ngx_cycle_s中 ( typedef struct ngx_cycle_s ngx_cycle_t)
struct ngx_cycle_s { void ****conf_ctx; //配置數據的起始存儲位置 ngx_pool_t *pool; ngx_log_t *log; ngx_log_t new_log; ngx_uint_t log_use_stderr; /* unsigned log_use_stderr:1; */ ngx_connection_t **files; ngx_connection_t *free_connections; ngx_uint_t free_connection_n; ngx_module_t **modules; ngx_uint_t modules_n; ngx_uint_t modules_used; /* unsigned modules_used:1; */ ngx_queue_t reusable_connections_queue; ngx_uint_t reusable_connections_n; ngx_array_t listening; ngx_array_t paths; ngx_array_t config_dump; ngx_rbtree_t config_dump_rbtree; ngx_rbtree_node_t config_dump_sentinel; ngx_list_t open_files; ngx_list_t shared_memory; ngx_uint_t connection_n; ngx_uint_t files_n; ngx_connection_t *connections; ngx_event_t *read_events; ngx_event_t *write_events; ngx_cycle_t *old_cycle; ngx_str_t conf_file; ngx_str_t conf_param; ngx_str_t conf_prefix; ngx_str_t prefix; ngx_str_t lock_file; ngx_str_t hostname; };
ngx_get_conf是個函數宏
#define ngx_get_conf(conf_ctx, module) conf_ctx[module.index]
從conf_ctx配置結構數組裏獲取當前配置的位置,索引是module.index,也就是ngx_module_t結構裏的index成員,index標記了模塊在modules數組裏的索引位置。objs/ngx_modules.c中能夠查看這些modules
3、初識結語
反覆的層級遞歸才粗略的摸清其中的結構,革命還沒有成功,仍需繼續!
參考資料
《Nginx徹底開發指南 使用C、C++和OpenResty》---羅劍鋒
《深刻理解Nginx 模塊開發與架構解析 第二版》---陶輝