【Nginx】初识nginx---实现一个简单的http模块

时间:2022-04-23 22:20:37
       Nginx是由俄罗斯软件工程师Igor Sysoev开发的一个高性能的HTTP和反向代理服务器,具备IMAP/POP3和SMTP服务器功能。Nginx最大的特点是对高并发的支持和高效的负载均衡,在高并发的需求场景下,是Apache服务器不错的替代品。目前,包括新浪、腾讯等知名网站已经开始使用Nginx作为Web应用服务器,淘宝开发的tengine也是基于Nginx开发的。
  

Nginx的安装很简单,下载源码包解压,进入Nginx目录,执行以下命令

./configure

make

make install

在linux中,需要使用命令行来控制Nginx的启动与停止、重新加载配置文件、回滚日志文件、平滑升级等操作。默认情况下Nginx被安装到/usr/local/nginx/中,可执行文件是/usr/local/nginx/sbin/nginx,配置文件是/urs/local/nginx/conf/nginx.conf,我安装的时候把ngnix安装在自己的nginx目录/home/yujian/nginx下。目录结构如下:

常见的命令有以下几个:

1、启动Nginx

./nginx

2、停止Nginx

./nginx -s stop(快速方式:发送SIGINT或是SIGTERM信号直接杀死进程)

./nginx -s quit(优雅方式:发送SIGQUIT信号,此时服务器会关闭监听端口,停止接受新的连接,然后把当前处理的连接全部处理完成,最后才退出进程)

3、重新读取配置文件

./ngnix -s reload

当重新加载配置文件时,Nginx会首先检查配置文件是否有语法错误,如果没有错误,就以“优雅的”方式关闭再重启Nginx。


Nginx的配置更为灵活,由配置块来组织配置项,并将配置块分为main,srv,loc等级别,便于配置文件的解析和管理,Nginx的配置文件时一个普通的文本,他的基本配置语法是

配置项名 值1 值2 ......(注意当当值中包含语法符号时,需要用当引号或双引号括起来)

以上就是Nginx安装和配置的一些基本知识,为了编写一个简单的http模块,需要了解Nginx是如何如何处理http的,以及他如何介入处理用户自己模块。

Nginx一般是通过master/worker方式工作的,master负责管理所有的worker,而所有的业务处理是有每一个worker完成的。worker会在自己的主循环里面反复调用时间检测模块来检测网络事件,当有连接请求时,worker负责建立连接,并根据配置交由http框架处理,http框架会试图接受完整的http头部,在接受完头部后,http框架将请求分发到具体的http模块中处理,自己编写的http模块就在此时交介入整个处理流程。

要编写Nginx的http模块,需要了解ngx_module_t、ngx_http_module_t、ngx_command_t等几个基本的数据结构,这里就不详述了,我会在代码中做出详细的解释,下面就开始我们的http模块之旅吧!

我们考虑最简单的情况,在loc块中配置自己的配置项mytest,当遇到mytest时,掉用自己的处理函数,响应用户请求。在Nginx的配置块中,模块处理请求的顺序是固定的,因为http框架定义了11中阶段,可以在每一个阶段中介入http框架,通常的http模块都是在HGX_HTTP_CONTENT_PHASE阶段介入的。我们接选择这种方式。

一、定义配置项的处理

假设我们在loc块中有如下的定义

[cpp] view plaincopyprint?
  1. location /test{  
  2.     mytest;  
  3. }  

我们需要定义一个ngx_command_t类型的数组,说明配置项的名称以及解析配置项的回调函数,定义如下:

[cpp] view plaincopyprint?
  1. //定义模块配置文件的处理   
  2. static ngx_command_t ngx_http_mytest_commands[] = {    
  3.      {  //配置项名称  
  4.          ngx_string("mytest"),   
  5.     //配置项类型,即定义他可以出现的位置   
  6.          NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS,    
  7.         //处理配置项参数的函数,函数在下面定义   
  8.     ngx_http_mytest,    
  9.     //在配置文件中的偏移量  
  10.          NGX_HTTP_LOC_CONF_OFFSET,  
  11.     //预设的解析方法配置项    
  12.          0,    
  13.     //配置项读取后的处理方法  
  14.          NULL    
  15.      },    
  16.      //command数组要以ngx_null_command结束  
  17.      //#define ngx_null_command {ngx_null_string,0,NULL,0,0,NULL}  
  18.      ngx_null_command    
  19.  };    

二、定义mytest模块的初始化时的回调方法,这个结构主要用来在解析配置文件时使用,在每一个main、srv、loc块中 用它的回调方法,产生自己配置结构的地址,供http框架使用,保存各个块的配置项,这里我们并没有需要解析的配置项,所以我们简单的把所有方法置NULL。定义如下:

[cpp] view plaincopyprint?
  1. //mytest模块上下文,都为NULL即是说在http框架初始化时没有什么要做    
  2. static ngx_http_module_t ngx_http_mytest_module_ctx = {    
  3.     NULL,  //preconfiguration  
  4.     NULL,  //postconfiguration  
  5.     NULL,  //create main configuration  
  6.     NULL,  //init main configuration  
  7.     NULL,  //create server configuration  
  8.     NULL,  //merge server configuration  
  9.     NULL,  //create location configuration  
  10.     NULL  //merge location configuration  
  11. };   

三、 对自己mytest模块的定义,在编译时会将这个模块加入到全局的ngx_modules数组中,这样在Nginx初始化时会调用模块的所有初始化方法,(上面的ngx_http_module_t类型的ngx_http_mytest_module_ctx)

[cpp] view plaincopyprint?
  1. ngx_module_t ngx_http_mytest_module = {    
  2.     NGX_MODULE_V1, //由Nginx定义的宏来初始化前七个成员   
  3.     &ngx_http_mytest_module_ctx,  //模块的上下文结构体,指向特定模块的公共方法  
  4.     ngx_http_mytest_commands,  //处理配置项的结构体数组  
  5.     NGX_HTTP_MODULE,  //模块类型  
  6.    //Nginx在启动停止过程中七个执行点的函数指针  
  7.     NULL,    
  8.     NULL,    
  9.     NULL,    
  10.     NULL,    
  11.     NULL,    
  12.     NULL,    
  13.     NULL,    
  14.   
  15.     NGX_MODULE_V1_PADDING  //由Nginx定义的宏定义剩下的8个保留字段  
  16. };    

    四、处理用户请求

通过上面的结构定义,当在location块中出现mytest配置项时,ngx_http_mytest函数会被调用,这时会将ngx_http_core_loc_conf_t结构的handler成员指针指向自己处理请求函数,http框架在接收完所有头部后就会调用handler指向的方法,handler原型如下:

typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);

ngx_http_request_t类型包含了所有本次请求的所有头和包体,以及一个与之相关联的一个内存池对象,用于请求响应的内存分配,我想这也是Nginx能够很好提高内存使用的一个成功之处。

[cpp] view plaincopyprint?
  1. //配置项对应的回调函数,当配置项中出现mytest配置项时将调用这个函数    
  2. static char *  ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)    
  3. {    //ckcf并不是指特定的location块内的数据结构,他可以是mian、srv、loc级别的配置项  
  4. //每个http{},sever{},location{}都有一个ngx_http_core_loc_conf_t类型的数据结构  
  5.         ngx_http_core_loc_conf_t *clcf;    
  6.       
  7. //找到mytest配置项所在的配置块  
  8.     clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);    
  9.       
  10. //http框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段是,如果请求的主机名,URI与配置项所在的配置块相匹配时,就调用  
  11. //clcf中的handle方法处理这个请求  
  12.         //NGX_HTTP_CONTENT_PHASE用于处理http请求内容的阶段,这是大部分http模块通常介入的阶段  
  13. clcf->handler = ngx_http_mytest_handler;    
  14.     
  15.      return NGX_CONF_OK;    
  16. }    

我们的处理请求函数只是简单的丢弃请求,并发送自己构造的响应。定义的用户请求的处理函数如下:

[cpp] view plaincopyprint?
  1. //实际完成处理的回调函数    
  2. static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)    
  3. {    
  4. /请求方法  
  5.     if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD))) {    
  6.         return NGX_HTTP_NOT_ALLOWED;    
  7.     }    
  8.   //不处理请求的包体,直接丢弃。但这一步也是不可省略的,他是接受包体的一种方法,只不过是简单的丢弃,  
  9.   //如果不接受,客户端可能会再次试图发送包体,而服务器不接受就会造成客户端发送超时  
  10.     ngx_int_t rc = ngx_http_discard_request_body(r);    
  11.     if (rc != NGX_OK) {    
  12.         return rc;    
  13.     }    
  14.   //构造响应头部  
  15.     ngx_str_t type = ngx_string("text/plain");    
  16.     ngx_str_t response = ngx_string("hello world ! \n\rthis is my first Nginx module test ! ");    
  17.     r->headers_out.status = NGX_HTTP_OK;    
  18.     r->headers_out.content_length_n = response.len;    
  19.     r->headers_out.content_type = type;    
  20.   //发送http头部,其中也包括响应行  
  21.     rc = ngx_http_send_header(r);    
  22.     if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {    
  23.         return rc;    
  24.     }    
  25.     
  26.    ngx_buf_t *b;    
  27.    //根据请求中传来的内存池对象,创建内存buf  
  28.    b = ngx_create_temp_buf(r->pool, response.len);    
  29.     if (b == NULL) {    
  30.         return NGX_HTTP_INTERNAL_SERVER_ERROR;    
  31.     }    
  32.   //有效内容从pos位置开始,复制respon的内容  
  33.     ngx_memcpy(b->pos, response.data, response.len);    
  34.     //有效内容到last结束  
  35.     b->last = b->pos + response.len;    
  36.     //因为ngx_buf_t可以由ngx_chain_t链表链起来,last_buf可以标记这是最后一块待处理的缓冲区,简化处理  
  37.     b->last_buf = 1;    
  38.  //将内存buf用链表链起来,作为ngx_http_output_filter的跌入个参数  
  39.     ngx_chain_t out;    
  40.     out.buf = b;    
  41.   //标记这是最后一个ngx_chain_t  
  42.     out.next = NULL;    
  43.     
  44.     return ngx_http_output_filter(r, &out);    
  45. }    

到此所有的模块都定义结束了,下面就是如何把自己的模块编译到Nginx中

五、把自己的模块编译到Nginx中

Nginx提供了多种方式将第三方模块编译进Nginx中,这里我们利用configure 脚本,将自己的模块加入。

首先,把所有的源代码放进一个文件夹,并在文件夹下新建一个config文件,config文件配置如下:

[cpp] view plaincopyprint?
  1. #仅在configure执行时使用,一般设置为模块名称  
  2. ngx_addon_name=ngx_http_mytest_module    
  3. #HTTP_MODULES保存所有的模块名称,在重设HTTP_MODULES时不能直接覆盖,而是先取得以前的HTTP_MODULES,在加上自己的模块  
  4. HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"   
  5. #制定新增源代码文件  
  6. NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"   

    在执行configure的时候需要添加参数--add-module=PATH,制定第三发模块源代码的目录,之后make、make install 就OK了 ,自己编写的简单http模块就完成了,下面是执行效果:

【Nginx】初识nginx---实现一个简单的http模块

注意,要是想调试自己的代码,需要配置一下几个配置项

daemon off;//关闭守护进程模式,这样就可以输出控制信息到控制台

master_process off;//多进程的调试很难,这个选项会让master进程自己处理用户请求,而不fock出worker子进程,方便调试。

err_log /path/file level//指定日志位置,和输出级别,将级别定位debug就可以输出所有信息,可以方便定位错误


完整的代码如下:


[cpp] view plaincopyprint?
  1. #include <ngx_config.h>    
  2. #include <ngx_core.h>    
  3. #include <ngx_http.h>    
  4.   
  5.  static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r);    
  6.  static char *    ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);  
  7.   
  8. //定义模块配置文件的处理   
  9. static ngx_command_t ngx_http_mytest_commands[] = {    
  10.      {  //配置项名称  
  11.          ngx_string("mytest"),   
  12.     //配置项类型,即定义他可以出现的位置   
  13.          NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS,    
  14.         //处理配置项参数的函数,函数在下面定义   
  15.     ngx_http_mytest,    
  16.     //在配置文件中的偏移量  
  17.          NGX_HTTP_LOC_CONF_OFFSET,  
  18.     //预设的解析方法配置项    
  19.          0,    
  20.     //配置项读取后的处理方法  
  21.          NULL    
  22.      },    
  23.      //command数组要以ngx_null_command结束  
  24.      //#define ngx_null_command {ngx_null_string,0,NULL,0,0,NULL}  
  25.      ngx_null_command    
  26.  };    
  27.   
  28.   
  29.  //mytest模块上下文,都为NULL即是说在http框架初始化时没有什么要做    
  30.  static ngx_http_module_t ngx_http_mytest_module_ctx = {    
  31.      NULL,  //preconfiguration  
  32.      NULL,  //postconfiguration  
  33.      NULL,  //create main configuration  
  34.      NULL,  //init main configuration  
  35.      NULL,  //create server configuration  
  36.      NULL,  //merge server configuration  
  37.      NULL,  //create location configuration  
  38.      NULL  //merge location configuration  
  39.  };    
  40.  //对自己mytest模块的定义,在编译时加入到全局的ngx_modules数组中,这样在Nginx初始化时会调用模块的所有初始化方法,(上面的ngx_http_module_t类型的ngx_http_mytest_module_ctx)  
  41.     
  42.  ngx_module_t ngx_http_mytest_module = {    
  43.      NGX_MODULE_V1, //由Nginx定义的宏来初始化前七个成员   
  44.      &ngx_http_mytest_module_ctx,  //模块的上下文结构体,指向特定模块的公共方法  
  45.      ngx_http_mytest_commands,  //处理配置项的结构体数组  
  46.      NGX_HTTP_MODULE,  //模块类型  
  47.     //Nginx在启动停止过程中七个执行点的函数指针  
  48.      NULL,    
  49.      NULL,    
  50.      NULL,    
  51.      NULL,    
  52.      NULL,    
  53.      NULL,    
  54.      NULL,    
  55.   
  56.      NGX_MODULE_V1_PADDING  //由Nginx定义的宏定义剩下的8个保留字段  
  57.  };    
  58.      
  59.  //配置项对应的回调函数,当配置项中出现mytest配置项时将调用这个函数    
  60.  static char *  ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)    
  61.  {   //ckcf并不是指特定的location块内的数据结构,他可以是mian、srv、loc级别的配置项  
  62.     //每个http{},sever{},location{}都有一个ngx_http_core_loc_conf_t类型的数据结构  
  63.         ngx_http_core_loc_conf_t *clcf;    
  64.       
  65.     //找到mytest配置项所在的配置块  
  66.         clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);    
  67.       
  68.     //http框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段是,如果请求的主机名,URI与配置项所在的配置块相匹配时,就调用  
  69.     //clcf中的handle方法处理这个请求  
  70.         //NGX_HTTP_CONTENT_PHASE用于处理http请求内容的阶段,这是大部分http模块通常介入的阶段  
  71.     clcf->handler = ngx_http_mytest_handler;    
  72.      
  73.          return NGX_CONF_OK;    
  74.  }    
  75.       
  76.   //实际完成处理的回调函数    
  77.   static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)    
  78.   {    
  79.     //请求方法  
  80.       if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD))) {    
  81.           return NGX_HTTP_NOT_ALLOWED;    
  82.       }    
  83.     //不处理请求的包体,直接丢弃。但这一步也是不可省略的,他是接受包体的一种方法,只不过是简单的丢弃,  
  84.     //如果不接受,客户端可能会再次试图发送包体,而服务器不接受就会造成客户端发送超时  
  85.       ngx_int_t rc = ngx_http_discard_request_body(r);    
  86.       if (rc != NGX_OK) {    
  87.           return rc;    
  88.       }    
  89.     //构造响应头部  
  90.       ngx_str_t type = ngx_string("text/plain");    
  91.       ngx_str_t response = ngx_string("hello world ! \n\rthis is my first Nginx module test ! ");    
  92.       r->headers_out.status = NGX_HTTP_OK;    
  93.       r->headers_out.content_length_n = response.len;    
  94.       r->headers_out.content_type = type;    
  95.     //发送http头部,其中也包括响应行  
  96.       rc = ngx_http_send_header(r);    
  97.       if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {    
  98.           return rc;    
  99.       }    
  100.       
  101.      ngx_buf_t *b;    
  102.      //根据请求中传来的内存池对象,创建内存buf  
  103.      b = ngx_create_temp_buf(r->pool, response.len);    
  104.       if (b == NULL) {    
  105.           return NGX_HTTP_INTERNAL_SERVER_ERROR;    
  106.       }    
  107.     //有效内容从pos位置开始,复制respon的内容  
  108.       ngx_memcpy(b->pos, response.data, response.len);    
  109.       //有效内容到last结束  
  110.       b->last = b->pos + response.len;    
  111.       //因为ngx_buf_t可以由ngx_chain_t链表链起来,last_buf可以标记这是最后一块待处理的缓冲区,简化处理  
  112.       b->last_buf = 1;    
  113.    //将内存buf用链表链起来,作为ngx_http_output_filter的跌入个参数  
  114.       ngx_chain_t out;    
  115.       out.buf = b;    
  116.     //标记这是最后一个ngx_chain_t  
  117.       out.next = NULL;    
  118.       
  119.       return ngx_http_output_filter(r, &out);    
  120.   }