模块链(Nginx 模块开发)
Posted
篇首语:少年恃险若平地,独倚长剑凌清秋。本文由小常识网(cha138.com)小编为大家整理,主要介绍了模块链(Nginx 模块开发)相关的知识,希望对你有一定的参考价值。
模块链(Nginx 模块开发)
一、在了解Nginx模块开发前,首先得知道在Nginx中http初始化流程、11个状态机、http请求具体流程。
(1)conf文件加载
对conf文件内容进行初始化,在命令行执行nginx -c ./conf/nginx.conf的之后,开始解析conf文件,启动http模块(入口:ngx_http_block)。
(2)状态机初始化
ngx_http_init_phase_handlers,保存状态机每个阶段需要处理的handler。
(3)tcp server启动
ngx_http_optimize_servers里面ngx_http_init_listening监听多个端口,这里是在master进程监听。
(4)http处理流程
业务处理逻辑是在worker进程处理,每一个worker都有一个ngx_worker_process_cycle()函数无限循环的处理,这个函数处理流程:
(a)accpet_cb接收和处理事件
(b)处理 request 的 header 和 body
(c)产生响应,并发送回客户端
一个HTTP Request 的处理过程涉及到以下几个阶段:
(a)初始化 HTTP Request(读取来自客户端的数据,生成 HTTP Request 对象);
(b)处理请求头;
(c)处理请求体;
(d)调用与此请求(location)关联的handler;
(e)依次调用各phase handler进行处理。
每一个phase就是一个阶段/状态,每个状态包含若干个handler,依次调用该状态的handler对HTTP Request进行处理,包括获取location配置、产生适当的响应、发送response header、发送response body
二、Nginx模块包括:event、handler、filter、upstream、load-balancer等,本文主要讲handler、filter、upstream等模块的开发;对于这3种模块的开发,模块配置指令结构、模块上下文写法类似。
(1)handler模块
接收客户端请求并产生输出的模块,每个模块就需要定义三个不同的数据结构去进行存储。
(a)模块配置信息存储结构
//以hello模块为例typedef structngx_str_t hello_string;ngx_int_t hello_counter;ngx_http_hello_loc_conf_t;
(b)模块配置指令结构
struct ngx_command_s ngx_str_t name;//配置指令的名称ngx_uint_t type;//该配置的类型,配置接收参数个数char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);//处理指令handlerngx_uint_t conf;ngx_uint_t offset;void *post;;
一个模块的配置指令是定义在一个静态数组中,包括了指令的名称、指令类型或接收的参数、处理指令的handler(函数指针,当 nginx 在解析配置的时候,如果遇到这个配置指令,将会把读取到的值传递给这个函数进行分解处理)
(c)模块上下文结构
//模块上下文结构体,存放回调函数指针,根据需求,添加不同状态相应的处理,否则填NULLstatic ngx_http_module_t ctx = ngx_int_t (*preconfiguration)(ngx_conf_t *cf),//在创建和读取该模块的配置信息之前被调用ngx_int_t (*postconfiguration)(ngx_conf_t *cf),//在创建和读取该模块的配置信息之后被调用void *(*create_main_conf)(ngx_conf_t *cf),//调用该函数创建本模块位于 http block 的配置信息存储结构char *(*init_main_conf)(ngx_conf_t *cf, void *conf),//调用该函数初始化本模块位于 http block 的配置信息存储结构void *(*create_srv_conf)(ngx_conf_t *cf),//调用该函数创建本模块位于 http server block 的配置信息存储结构,每个 server block 会创建一个char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf),//有些配置指令既可以出现在 http block,也可以出现在 http server block 中。那么遇到这种情况,每个 server都会有自己存储结构来存储该 server 的配置,但是在这种情况下 http block 中的配置与 server block 中的配置信息发生冲突的时候,就需要调用此函数进行合并void *(*create_loc_conf)(ngx_conf_t *cf),//调用该函数创建本模块位于 location block 的配置信息存储结构char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf)//与 merge_srv_conf 类似,这个也是进行配置值合并的地方;
通过上面8个回调函数,知道http状态信息来源conf文件解析,conf文件中http、server、location对应着以上相应的回调函数。
【文章福利】另外小编还整理了一些C/C++后台开发教学视频,相关面试题,后台学习路线图免费分享,需要的可以自行添加:点击 正在跳转 加入~群文件共享
小编强力推荐C++后台开发免费学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
模块的定义:
//模块定义ngx_module_t ngx_http_pagecount_module = NGX_MODULE_V1,//版本,宏定义 &count_ctx,//模块上下文 count_commands,//模块配置指令 NGX_HTTP_MODULE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NGX_MODULE_V1_PADDING//宏定义;
任何模块都可以从两个角度分析,一个是抓住执行nginx -c conf/nginx.conf,模块配置指令的入口handler,另一个就接收客户端请求时,模块执行的handler。
挂载handler,一种是按处理阶段挂载;另外一种挂载方式就是按需挂载。
(a)按处理阶段挂载,就是根据需求,可挂载在11个状态机的某一个阶段(在上下文结构体中)
(b)按需挂载,也被称为 content handler,某个模块对某个 location 进行了处理以后,发现符合自己处理的逻辑,而且也没有必要再调用 NGX_HTTP_CONTENT_PHASE 阶段的其它 handler 进行处理的时候,就动态挂载上这个 handler。
(2)Filter模块
过滤模块是过滤响应头和内容的模块,可以对回复的头和内容进行处理。过滤模块的调用是有顺序的,它的顺序在编译时就已经确定,可以打开ngx_module.c文件,按着ngx_modules数组逆序执行。在过滤模块中,所有输出的内容都是通过一条单链表所组成,Nginx都是读到一部分的内容就放到链表,然后输出出去(ngx_http_write_filter_module)。
ngx_http_top_header_filter 是一个全局变量。当编译进一个 filter 模块的时候,就被赋值为当前 filter 模块的处理函数。而 ngx_http_next_header_filter 是一个局部全局变量,它保存了编译前上一个 filter 模块的处理函数。所以整体看来,就像用全局变量组成的一条单向链表。每个模块想执行下一个过滤函数,只要调用一下 ngx_http_next_header_filter 这个局部变量。而整个过滤模块链的入口,需要调用 ngx_http_top_header_filter 这个全局变量。ngx_http_top_body_filter 的行为与 header fitler 类似在初始化时,设置过滤块链的入口。
//解析完conf后,设置运行时的回调函数//使用头插法,将header_filter和body_filter插入filter队列头部static ngx_int_t ngx_http_myfilter_init(ngx_conf_t *cf) ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_myfilter_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_myfilter_body_filter; return NGX_OK;
响应头过滤主要的用处是处理http响应头,可以根据实际情况对响应头进行修改、添加或删除,而且只调用一次,所以一般可作过滤模块的初始化工作,入口函数如下:
static ngx_int_t ngx_http_myfilter_header_filter(ngx_http_request_t *r) .......... return ngx_http_top_header_filter(r);
Nginx把 HTTP响应头的存储方式想象成一个 hash 表,在 Nginx 内部可以很方便地查找和修改各个响应头部,ngx_http_header_filter_module 过滤模块把所有的 HTTP 头组合成一个完整的 buffer,最终 ngx_http_write_filter_module 过滤模块把 buffer 输出。
响应体过滤函数,ngx_http_top_body_filter (指向ngx_http_myfilter_body_filter)这个函数每个请求可能会被执行多次,响应体过滤函数的格式类似这样:
static ngx_int_t ngx_http_myfilter_body_filter(ngx_http_request_t *r, ngx_chain_t *in) .......... return ngx_http_next_body_filter(r,c1);
(3)Upstream模块
upstream模块实现反向代理的功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回客户端;upstream模块是一种特殊的handler,只不过响应内容不是由自己产生,而是从后端服务器上读取的。
upstream一开始的配置指令、模块上下文等都与handler类似,在 memcached 的例子中,可以观察 ngx_http_memcached_handler 的代码,可以发现,这个固定的操作流程是
a.创建upstream数据结构
if (ngx_http_upstream_create(r) != NGX_OK) return NGX_HTTP_INTERNAL_SERVER_ERROR;
b.设置模块的tag和schema
u = r->upstream;ngx_str_set(&u->schema, "memcached://");u->output.tag = (ngx_buf_tag_t) &ngx_http_memcached_module;
c.设置upstream的后端服务器列表数据结构
mlcf = ngx_http_get_module_loc_conf(r,ngx_http_memcached_module);u->conf = &mlcf->upstream;
d.设置upstream回调函数
upstream的回调函数列表如下:
此时,回调函数设置如下:
u->create_request = ngx_http_memcached_create_request;u->reinit_request = ngx_http_memcached_reinit_request;u->process_header = ngx_http_memcached_process_header;u->abort_request = ngx_http_memcached_abort_request;u->finalize_request = ngx_http_memcached_finalize_request;u->input_filter_init = ngx_http_memcached_filter_init;u->input_filter = ngx_http_memcached_filter;
e.创建并设置upstream环境数据结构
ctx = ngx_palloc(r->pool, sizeof(ngx_http_memcached_ctx_t));if (ctx == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;ctx->rest = NGX_HTTP_MEMCACHED_END;ctx->request = r;ngx_http_set_ctx(r, ctx, ngx_http_memcached_module);u->input_filter_ctx = ctx;
f.完成upstream初始化并进行收尾工作
r->main->count++;ngx_http_upstream_init(r);return NGX_DONE;
其中第 b、d两步很容易理解,不同的模块设置的标志和使用的回调函数肯定不同。第 e步也不难理解,只有第 c步是最为晦涩的,不同的模块在取得后端服务器列表时,策略的差异非常大,有如 memcached 这样简单明了的,也有如 proxy 那样逻辑复杂的。这个问题先记下来,等把 memcached 剖析清楚了,再单独讨论。
upstream 模块是从 handler 模块发展而来,指令系统和模块生效方式与 handler 模块无异。不同之处在于,upstream 模块在 handler 函数中设置众多回调函数。实际工作都是由这些回调函数完成的。每个回调函数都是在 upstream的某个固定阶段执行,各司其职,大部分回调函数一般不会真正用到。 upstream 最重要的回调函数是 create_request 、process_header 和 input_filter,他们共同实现了与后端服务器的协议的解析部分。
参考资料
推荐一个零声教育C/C++后台开发的免费公开课程,个人觉得老师讲得不错,分享给大家:C/C++后台开发高级架构师,内容包括Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂 立即学习
原文链接:Nginx 模块开发 - MrJuJu - 博客园
相关参考
网带输送机是新出现的一种输送设备,它的结构组成包括:网带、输送机架、传动系统、输送系统动力总成等。跟着现代社会经济高速的发展,各个行业也迅速发展起来。不少国家正在探索长间隔、大运量连续输送物料的更完善...
模块塑料网链(你家消毒品存放安全吗?居家消毒这些要点需注意)
2020年的新冠肺炎疫情,让大家变成了消毒小卫士,大多数家庭都备有84消毒液、75%酒精等消毒用品等。但消毒也是把双刃剑,存放不好,使用不当可能会给自己带来祸端,今天人民健康网给你科普一下有关消毒的问题。 消毒...
“加强新型基础设施建设,发展新一代信息网络,拓展5G应用,建设充电桩,推广新能源汽车,激发新消费需求、助力产业升级。”今年政府工作报告,首次纳入“新基建”,成为今年国家发展大局的重要组成部分。当前,受新...
模块网塑料链(废塑有新出路美国、英国、印度、荷兰等国家都修建塑料公路)
在美国加州奥罗维尔(Oroville)地区新建成的一条高速公路,看上去与普通的高速公路并无区别,但这是美国第一条采用了再生塑料铺成的高速公路,三车道的高速公路,每英里(相当于1.6公里)约使用了15万个废塑料瓶。据介...
什么是模块化开发?模块化开发其实就是将程序划分为不同的相互独立的模块单元,然后将这些模块进行整合,这样每个模块皆既可以单独使用,也可以进行组合使用,彼此之间互不影响,代码耦合度降低。意义:对于企业系统...
树莓派基金会近日宣布推出树莓派计算模块4,这是树莓派基金会新推出的定制计算模块。新的计算模块起售价为25美元,根据用户不同的硬件搭配最高价达90美元,而计算模块4IO开发板售价35美元。同时树莓派基金会还会新的计算模...
开发指纹模块(基于STM32 MCU和串口TFT LCD模块的指纹识别)
...篇客座文章,STONE技术公司是一家专门从事工业液晶显示模块的公司。2019年11月,我计划开发一个指纹门锁项目。当我选择好指纹识别模块时,项目就被暂停了。不过,我想既然已经购买了指纹识别模块,那还是简单地测试一下...
无线传输模块有哪些(环旭电子与客户合作开发的无线通信与系统级模块,开始有少量出货)
...的智能眼镜、AR/VR相关产品的无线通信(BT/WiFi6E)与系统级模块,尚在合作研发阶段,今年开始有少量产品出货。”环旭电子此前还表示,公司正持续开展企业创投(CVC)投资,已投资GaN芯片、WiFi6芯片、ICT产业基金等项目。公司的CVC...
日立配件(天龙股份:开发的IGBT功能承载模块目前已收到日立集团小批量订单)
...互动平台表示,公司为客户开发的定制化的IGBT功能承载模块,是应用于新能源汽车PCU动力控制单元核心部件之一,目前已收到日立集团小批量订单,已顺利量产,该项目目前间接供货于本田下一代混动类新能源汽车,同时公司...
模块治具(机床夹具非标件的模块化设计,工装夹具机床夹具的选择和使用)
机床夹具非标件的模块化设计计算机辅助设计CAD已广泛应用在工程设计领域。图形库的开发是工装CAD中的关键技术。它的优劣将直接关系到CAD系统工作效率的高低,而开发图形库所遇到的一个难题就是非标件的模块化设计。笔者...