1。Nginx模块1。1Nginx中的模块化设计 Nginx的内部结构是由核心部分和一系列的功能模块所组成。这样划分是为了使得每个模块的功能相对简单,便于开发,同时也便于对系统进行功能扩展。Nginx将各功能模块组织成一条链,当有请求到达的时候,请求依次经过这条链上的部分或者全部模块,进行处理。例如前面讲到的http请求,会有11个处理阶段,而每个阶段有对应着许多在此阶段生效的模块对该http请求进行处理。同时,Nginx开放了第三方模块编写功能,用户可以自定义模块,控制http请求的处理与响应,这种高度可定制化催生了Nginx的大量第三方模块,也使得Nginx定制化开发在各大互联网公司十分流行。1。2Nginx中的模块分类 关于Nginx模块的分类有很多种方式,目前网上博客中写的较多的是按照功能进行分类,有如下几大类:event模块:搭建独立于操作系统的事件处理机制的框架,以及提供各种具体事件的处理。代表性的模块有:ngxeventsmodule,ngxeventcoremodule,handler模块:主要负责处理客户端请求并产生待响应的内容,比如ngxhttpstaticmodule模块,负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出;filter模块:主要负责处理输出的内容,包括修改输出内容。代表性的模块有:upstream模块:该类模块都是用于实现反向代理功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回给客户端。比如前面介绍到转发http、websocket、grpc、rtmp等协议的模块都可以划分为这一类;负载均衡模块:负载均衡的模块,实现相应算法。这类模块都是用于实现Nginx的负载均衡功能。extend模块:又称第三方模块,非Nginx官方提供,由各大企业的开发人员结合自身业务开发而成。Nginx提供了非常好的模块编写机制,遵循相关的标准可以很快定制出符合我们业务场景的模块,而且内部调用Nginx内部提供的方法进行处理,使得第三方模块往往都具备很好的性能 对于官方提供的模块,我们可以直接在官网文档上学习,学习的方式和学习其他互联网组件的方式一致,首先学习如何使用,在用至熟练后可以深入分析其源码了解功能实现背后的原理。 我们以前面介绍到的Nginx的限速模块(limitreq模块)进行说明。首先是掌握该模块的用法,在该模块的官方地址中,有关于该模块的详细介绍,包括该模块提供的所有指令以及所有变量说明。此外,还有比较丰富的指令用例。在多次使用该指令并自认为掌握了该模块的用法之后,想了解限速背后的原理以及相关算法时,就可以深入到源码学习了。 进入Nginx的源码目录,使用ls查看源码文件,限速模块是在http目录中的。〔rootservernginx1。17。6〕cdsrc〔rootserversrc〕lscoreeventhttpmailmiscosstream〔rootserversrc〕lshttpmodulesngxhttplimit。chttpmodulesngxhttplimitconnmodule。chttpmodulesngxhttplimitreqmodule。c代码块12345678 找到Nginx模块对应的代码文件后,我们就可以阅读里面的代码进行学习。往往源码的阅读是枯燥无味的,我们可以借助海量的网络资源辅助我们学习。这里就有一篇文章,作者深入分析了Nginx的限流模块的源码以及相应限流算法,最后进行了相关的实验测试。通过这样一个个模块深入学习,最后在使用每一个Nginx指令时,也会非常熟练,最后成为Nginx高手。1。3如何学习和使用第三方模块 这里我们演示在Nginx中使用第三方模块。Openresty社区提供了一款Nginx中的Echo模块,即echonginxmodule。在Nginx中添加了该模块后,我们在配置文件中可以使用该模块提供的echo指令返回用户响应,简单方便。该模块的源码在github上,并且有良好的文档和使用示例,非常方便开发者使用。 现在我们在Nginx的源码编译阶段加入该第三方模块,具体操作如下:〔rootservershencong〕pwdrootshencong〔rootservershencong〕mkdirnginxecho下载nginx源码包和第三方模块的源码包〔rootservershencong〕wgethttp:nginx。orgdownloadnginx1。17。6。tar。gz〔rootservershencong〕wgethttps:github。comopenrestyechonginxmodulearchivev0。62rc1。tar。gz解压〔rootservershencong〕tarxzfnginx1。17。6。tar。gz〔rootservershencong〕tarxzfv0。62rc1。tar。gz〔rootservershencong〕lsechonginxmodule0。62rc1nginx1。17。6nginx1。17。6。tar。gznginxechov0。62rc1。tar。gz〔rootservershencong〕cdnginx1。17。6使用addmodule添加第三方模块,参数为第三方模块源码〔rootservershencong〕。configureprefixrootshencongnginxechoaddmodulerootshencongechonginxmodule0。62rc1 编译完成后,我们就可以去nginxecho目录中的nginx。conf文件中添加echo指令。准备如下的配置(可以参参考社区提供的示例):。。。http{server{listen80;charsetkoi8r;accessloglogshost。access。location{indexindex。htmlindex。}新增测试echo指令配置locationtimedhello{echohelloworldtakesaboutechotimerelapsedsec。;echohiyaigortakesaboutechotimerelapsedsec。;}locationechowithsleep{ensuretheclientcanseepreviousoutputimmediatelyechosleep2。5;insecechohellotakesaboutechotimerelapsedsec。;}}}。。。 启动Nginx后,我们就可以在浏览器上请求者两个URI地址,看到相应echo返回的信息了。第二个配置是使用了echosleep指令,会使得请求在休眠2。5s后才返回。1。4如何编写自己的模块 想要编写Nginx模块,首先需要对Nginx模块中的源码以及相关的数据结构有所了解,还要知晓NginxHTTP模块的调用流程。假设我要实现前面第三方模块Echo的最简单形式,即只输出相应的字符串即可。假定模块支持的指令名称还是echo,这个echo指令需要跟一个参数,即输出的字符串。我们需要做如下几步:确定模块名称,以及模块中的指令以及参数,还有运行的环境。这里涉及的结构是ngxcommandt,它定义了模块里的所有指令格式。下面的代码表示该模块中只有一个echo指令,它出现的上下文环境为location,且有一个参数(NGXCONFTAKE1)。当某个配置块中出现echo指令时,Nginx将调用ngxhttpecho方法。然后在该方法中,会设置处理请求的handler,这个handler就是处理请求的方法。staticngxcommandtngxhttpechocommands〔〕{{ngxstring(echo),指令名称,利用ngxstring宏定义NGXHTTPLOCCONFNGXCONFTAKE1,用在location指令块内,且有1个参数ngxhttpecho,处理回调函数NGXHTTPLOCCONFOFFSET,offsetof(ngxhttpecholocconft,ed),指定参数读取位置NULL},ngxnullcommand};完成请求处理的handler函数,最重要的部分就在这里;staticcharngxhttpecho(ngxconftcf,ngxcommandtcmd,voidconf){找到指令所属的配置块,这里我们限定echo指令的上下文环境只有locationclcfngxhttpconfgetmodulelocconf(cf,ngxhttpcoremodule);指定处理的。。。returnNGXCONFOK;}staticngxinttngxhttpechohandler(ngxhttprequesttr){。。。向用户发送相应包returnngxhttpoutputfilter(r,out);}一些收尾工作,比如配置模块介入http请求的哪些阶段等。Httpcontextofthemodulestaticngxhttpmoduletngxhttpechomodulectx{NULL,preconfigurationNULL,postconfigurationNULL,createmainconfigurationNULL,initmainconfigurationNULL,createserverconfigurationNULL,mergeserverconfigurationngxhttpechocreatelocconf,createlocationconfigrationngxhttpechomergelocconfmergelocationconfigration};Modulengxmoduletngxhttpechomodule{NGXMODULEV1,ngxhttpechomodulectx,modulecontextngxhttpechocommands,moduledirectivesNGXHTTPMODULE,moduletypeNULL,initmasterNULL,initmoduleNULL,initprocessNULL,initthreadNULL,exitthreadNULL,exitprocessNULL,exitmasterNGXMODULEV1PADDING}; 完成以上几步,一个简易的自定义模块就算大功告成了。接下来我们动手完成第一个自定义的模块,将其编译进Nginx二进制文件中并进行测试。2。案例 我们来完成一个简单的自定义http模块,来实现前面Echo模块的最简单形式,即使用指令输出hello,world字符串。首先新建一个目录echonginxmodule,然后在目录下新建两个文件config和ngxhttpechomodule。c〔rootserverechonginxmodule〕pwdrootshencongechonginxmodule〔rootserverechonginxmodule〕lsconfigngxhttpechomodule。c 两个文件内容分别如下:〔rootserverechonginxmodule〕catconfigngxaddonnamengxhttpechomodule指定模块名称HTTPMODULESHTTPMODULESngxhttpechomodule指定模块源码路径NGXADDONSRCSNGXADDONSRCSngxaddondirngxhttpechomodule。c〔rootserverechonginxmodule〕catngxhttpechomodule。cincludengxconfig。hincludengxcore。hincludengxhttp。hModuleconfigtypedefstruct{}staticcharngxhttpecho(ngxconftcf,ngxcommandtcmd,voidconf);staticvoidngxhttpechocreatelocconf(ngxconftcf);staticcharngxhttpechomergelocconf(ngxconftcf,voidparent,voidchild);定义指令staticngxcommandtngxhttpechocommands〔〕{{ngxstring(echo),指令名称,利用ngxstring宏定义NGXHTTPLOCCONFNGXCONFTAKE1,用在location指令块内,且有1个参数ngxhttpecho,处理回调函数NGXHTTPLOCCONFOFFSET,offsetof(ngxhttpecholocconft,ed),指定参数读取位置NULL},ngxnullcommand};Httpcontextofthemodulestaticngxhttpmoduletngxhttpechomodulectx{NULL,preconfigurationNULL,postconfigurationNULL,createmainconfigurationNULL,initmainconfigurationNULL,createserverconfigurationNULL,mergeserverconfigurationngxhttpechocreatelocconf,createlocationconfigrationngxhttpechomergelocconfmergelocationconfigration};Modulengxmoduletngxhttpechomodule{NGXMODULEV1,ngxhttpechomodulectx,modulecontextngxhttpechocommands,moduledirectivesNGXHTTPMODULE,moduletypeNULL,initmasterNULL,initmoduleNULL,initprocessNULL,initthreadNULL,exitthreadNULL,exitprocessNULL,exitmasterNGXMODULEV1PADDING};Handlerfunctionstaticngxinttngxhttpechohandler(ngxhttprequesttr){获取指令的参数elcfngxhttpgetmodulelocconf(r,ngxhttpechomodule);if(!(rmethod(NGXHTTPHEADNGXHTTPGETNGXHTTPPOST))){如果不是HEADGETPUT请求,则返回405NotAllowed错误returnNGXHTTPNOTALLOWED;}rheadersout。contenttype。lensizeof(texthtml)1;rheadersout。contenttype。data(uchar)rheadersout。statusNGXHTTPOK;rheadersout。contentlengthnelcfed。if(rmethodNGXHTTPHEAD){rcngxhttpsendheader(r);if(rc!NGXOK){}}bngxpcalloc(rpool,sizeof(ngxbuft));if(bNULL){ngxlogerror(NGXLOGERR,rconnectionlog,0,Failedtoallocateresponsebuffer。);returnNGXHTTPINTERNALSERVERERROR;}out。out。nextNULL;bposelcfed。blastelcfed。data(elcfed。len);bmemory1;blastbuf1;rcngxhttpsendheader(r);if(rc!NGXOK){}向用户发送相应包returnngxhttpoutputfilter(r,out);}staticcharngxhttpecho(ngxconftcf,ngxcommandtcmd,voidconf){clcfngxhttpconfgetmodulelocconf(cf,ngxhttpcoremodule);指定处理的ngxconfsetstrslot(cf,cmd,conf);returnNGXCONFOK;}staticvoidngxhttpechocreatelocconf(ngxconftcf){confngxpcalloc(cfpool,sizeof(ngxhttpecholocconft));if(confNULL){returnNGXCONFERROR;}confed。len0;confed。dataNULL;}staticcharngxhttpechomergelocconf(ngxconftcf,voidparent,voidchild){ngxconfmergestrvalue(confed,preved,);returnNGXCONFOK;} 这样一个第三方模块包就完成了,接下来我们要向之前使用第三方模块一样,将它编译进Nginx,具体操作如下。〔rootservershencong〕cdnginx1。17。6〔rootservernginx1。17。6〕。configureprefixrootshencongnginxechoaddmodulerootshencongechonginxmodule。。。〔rootservernginx1。17。6〕makemakeinstall。。。〔rootservernginx1。17。6〕cd。。nginxechosbin〔rootserversbin〕。nginxVnginxversion:nginx1。17。6builtbygcc4。8。520150623(RedHat4。8。539)(GCC)configurearguments:prefixrootshencongnginxechoaddmodulerootshencongechonginxmodule 接下来,我们只要在nginx。conf中加入我们的指令,并给一个参数,就能看到我们自定义的输出了。。。。http{。。。server{listen80;location{indexindex。htmlindex。}locationtest{echohello,}。。。}}。。。 最后我们请求主机的80端口,URItest,浏览器输出hello,world,说明我们的自定义模块成功了! 3。小结 在Nginx基础架构介绍的最后,主要是介绍了Nginx的模块设计以及相应的模块用法。最后,我们简单给出了一个简单编写自己模块的案例作为本章的实战案例。希望这一节之后,大家能对Nginx的模块化设计有所了解,并有兴趣在模块的源码方向深入学习。