7.1 ngx_command_t 数组

commands 数组用于定义模块的配置文件参数,每一个数组元素都是ngx_command_t类型,数组的结尾是用ngx_numm_command表示。Nginx在解析配置文件中的一个配置项时首先会遍历所有的模块,对于每一个模块而言,即通过遍历commands数组进行,另外,在数组中检查到ngx_numm_command时,会停止使用当前模块解析该配置项。每一个ngx_command_t结构体定义个如下配置:

  1. typedef struct ngx_command_s ngx_command_t;
  2. struct ngx_command_s {
  3. //配置项名称,如"gzip"
  4. ngx_str_t name;
  5. //配置项类型,type将制定配置项可以出现的位置,
  6. //例如:出现在server{}活location{}中,等等
  7. ngx_uint_t type;
  8. //出现了name中指定的配置项后,将会调用set方法处理配置项的参数
  9. char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
  10. //在配置文件中的偏移量
  11. ngx_uint_t conf;
  12. //通常用于使用预设的解析方法解析配置项,这是配置模块的一个优秀的设计,
  13. //需要与conf配合使用
  14. ngx_uint_t offset;
  15. //配置项读取后的处理方法,必须是ngx_conf_post_t结构的指针
  16. void *post;
  17. };

ngx_null_command只是一个空的ngx_command_s:

  1. #define ngx_null_command {ngx_null_string, 0, NULL, 0, 0, NULL}

也就是说,对于我们在nginx.cong 中编写的配置项mytest来说,nginx首先会遍历所有的模块(modules),而对于每个模块,会遍历他所对应的ngx_command_t数组,试图找到关于我们配置项目mytest的解析方式。

  1. static ngx_command_t ngx_http_mytest_commands[] =
  2. {
  3. {
  4. ngx_string("mytest"),
  5. NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_NOARGS,
  6. ngx_http_mytest,
  7. NGX_HTTP_LOC_CONF_OFFSET,
  8. 0,
  9. NULL
  10. },
  11. ngx_null_command
  12. };

7.2 command中用于处理配置项参数的set方法

  1. static char *
  2. ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  3. {
  4. ngx_http_core_loc_conf_t *clcf;
  5. //首先找到mytest配置项所属的配置块,clcf貌似是location块内的数据
  6. //结构,其实不然,它可以是main、srv或者loc级别配置项,也就是说在每个
  7. //http{}和server{}内也都有一个ngx_http_core_loc_conf_t结构体
  8. clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
  9. //http框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时,如果
  10. //请求的主机域名、URI与mytest配置项所在的配置块相匹配,就将调用我们
  11. //实现的ngx_http_mytest_handler方法处理这个请求
  12. clcf->handler = ngx_http_mytest_handler;
  13. return NGX_CONF_OK;
  14. }

关于ngx_http_conf_get_module_loc_conf 的定义可以参考:

http://lxr.nginx.org/source/src/http/ngx_http_config.h#0065

本质: 就是设置ngx_http_mytest_handler, 匹配项被选中的时候, 应该如何解析。

7.3 定义ngx_http_module_t接口

这部分的代码, 是用于http框架的, 相当于http框架的回掉函数, 由于这里并不需要框架做任何操作。

  1. static ngx_http_module_t ngx_http_mytest_module_ctx =
  2. {
  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. };

7.4 定义处理“mytest”command的handler


我们这里需要定义配置项被匹配之后的处理方法.

  1. static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
  2. {
  3. //必须是GET或者HEAD方法,否则返回405 Not Allowed
  4. if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD)))
  5. {
  6. return NGX_HTTP_NOT_ALLOWED;
  7. }
  8. //丢弃请求中的包体
  9. ngx_int_t rc = ngx_http_discard_request_body(r);
  10. if (rc != NGX_OK)
  11. {
  12. return rc;
  13. }
  14. //设置返回的Content-Type。注意,ngx_str_t有一个很方便的初始化宏
  15. //ngx_string,它可以把ngx_str_t的data和len成员都设置好
  16. ngx_str_t type = ngx_string("text/plain");
  17. //返回的包体内容
  18. ngx_str_t response = ngx_string("Hello World!");
  19. //设置返回状态码
  20. r->headers_out.status = NGX_HTTP_OK;
  21. //响应包是有包体内容的,所以需要设置Content-Length长度
  22. r->headers_out.content_length_n = response.len;
  23. //设置Content-Type
  24. r->headers_out.content_type = type;
  25. //发送http头部
  26. rc = ngx_http_send_header(r);
  27. if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
  28. {
  29. return rc;
  30. }
  31. //构造ngx_buf_t结构准备发送包体
  32. ngx_buf_t *b;
  33. b = ngx_create_temp_buf(r->pool, response.len);
  34. if (b == NULL)
  35. {
  36. return NGX_HTTP_INTERNAL_SERVER_ERROR;
  37. }
  38. //将Hello World拷贝到ngx_buf_t指向的内存中
  39. ngx_memcpy(b->pos, response.data, response.len);
  40. //注意,一定要设置好last指针
  41. b->last = b->pos + response.len;
  42. //声明这是最后一块缓冲区
  43. b->last_buf = 1;
  44. //构造发送时的ngx_chain_t结构体
  45. ngx_chain_t out;
  46. //赋值ngx_buf_t
  47. out.buf = b;
  48. //设置next为NULL
  49. out.next = NULL;
  50. //最后一步发送包体,http框架会调ngx_http_finalize_request方法
  51. //结束请求
  52. return ngx_http_output_filter(r, &out);
  53. }

7.5 定义ngx_module_t中的mytest模块


定义HTTP模块方式很简单,例如:

  1. ngx_module_t ngx_http_mytest_module;

其中ngx_module_t是一个Nginx模块的数据结构,下面来分析一下Nginx模块中的所有成员:

  1. typedef struct ngx_module_s ngx_module_t;
  2. struct ngx_module_s {
  3. /*
  4. 下面的ctx_index、index、spare0,spare1,
  5. spare2,spare3,version变量不需要再定义时候赋值,
  6. 可以用Nginx准备好的宏NGX_MODULE_V1来定义,
  7. 已经定义好了这7个值
  8. #define NGX_MODULE_V1 0,0,0,0,0,0,1
  9. 对于一类模块(由下面的type成员决定类别)而言,
  10. ctx_index表示当前模块在这类模块中的序号。
  11. 这个成员常常是由管理这类模块的一个Nginx核心模块设置的,
  12. 对于所有的HTTP模块而言,ctx_index是由
  13. 核心模块ngx_http_module设置的。
  14. ctx_index非常重要,Nginx的模块化设计非常依赖于各个模块的顺序,
  15. 他们既用于表达优先级,也用于表明每个模块的位置,
  16. 借以帮助Nginx框架快速获得某个模块的数据.
  17. */
  18. ngx_uint_t ctx_index;
  19. /*
  20. index表示当前模块在ngx_module数组中的序号。
  21. 注意,ctx_index表示的是当前模块在一类模块中的序号,
  22. 而index表示当前模块在所有模块中的序号,同样很关键.
  23. Nginx启动时会根据ngx_modules数组设置各模块的index值,
  24. 例如:
  25. ngx_max_module = 0;
  26. for(i = 0; ngx_module[i]; i++) {
  27. ngx_modules[i]->index = ngx_max_module++;
  28. }
  29. */
  30. ngx_uint_t index;
  31. //spare系列的保留变量,暂未使用
  32. ngx_uint_t spare0;
  33. ngx_uint_t spare1;
  34. ngx_uint_t spare2;
  35. ngx_uint_t spare3;
  36. //模块的版本,便于将来的拓展,目前只有一种,默认为1
  37. ngx_uint_t version;
  38. /*
  39. ctx用于指向一类模块的上下文结构体,
  40. 为什么需要ctx呢?因为前面说过,
  41. Nginx模块有许多种类,不同类模块之间的功能差别很大。
  42. 例如,
  43. 事件类型的模块主要处理I/O事件的相关功能,
  44. HTTP类型的模块主要处理HTTP应用层的功能。
  45. 这样每个模块都有了自己的特性,
  46. 而ctx会指向特定类型模块的公共接口。
  47. 例如,
  48. 在HTTP模块中,ctx需要指向ngx_http_module_t结构体
  49. */
  50. void *ctx;
  51. /*
  52. commands处理nginx.conf中的配置项
  53. */
  54. ngx_command_t *commands;
  55. /*
  56. type表示该模块的类型,它与ctx指针是紧密相关的。
  57. 在官方Nginx中,它的取值范围有以下5种:
  58. NGX_HTTP_MODULE
  59. NGX_CORE_MODULE
  60. NGX_CONF_MODULE
  61. NGX_EVENT_MODULE
  62. NGX_MAIL_MODULE
  63. */
  64. ngx_uint_t type;
  65. /*
  66. 在Nginx的启动、停止过程中,以下7个函数指针表示7个
  67. 执行点分别用调用这7种方法。对于任意一个方法而言,
  68. 如果不需要Nginx再某个时刻执行它,那么简单地把它设为NULL
  69. 空指针即可。
  70. */
  71. /*
  72. 虽然从字面上理解应当在master进程启动的时,回调
  73. init_master,但到目前为止,框架代码从来不会调动它,
  74. 所以设置为NULL
  75. */
  76. ngx_int_t (*init_master)(ngx_log_t *log);
  77. /*
  78. init_module回调方法在初始化所有模块时候被调用。
  79. 在master/worker模式下,这个阶段将在启动worker子进程前完成。
  80. */
  81. ngx_int_t (*init_module)(ngx_cycle_t *cycle);
  82. /*
  83. init_process 回调方法在正常服务前被调用。
  84. 在master/worker模式下,多个worker子进程已经产生。
  85. 在每个worker进程的初始化过程会调用所有模块的init_process函数
  86. */
  87. ngx_int_t (*init_process)(ngx_cycle_t *cycle);
  88. /*
  89. 由于Nginx暂时不支持多线程模式,所以init_thread在框架中
  90. 没有被调用过,设置为NULL
  91. */
  92. ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
  93. /*
  94. 同上、exit_thread也不支持,设为NULL
  95. */
  96. void (*exit_thread)(ngx_cycle_t *cycle);
  97. /*
  98. exit_process回调方法在服务停止前被调用。
  99. 在/master/worker模式下,worker进程会在退出前调用
  100. */
  101. void (*exit_process)(ngx_cycle_t *cycle);
  102. /*
  103. exit_master回调方法将在master进程退出前被调用
  104. */
  105. void (*exit_master)(ngx_cycle_t *cycle);
  106. /*
  107. 一下8个spare_hook变量也是保留字段,目前没有使用,
  108. 但可用Nginx提供的NGX_MODULE_V1_PADDING宏来填充
  109. #define NGX_MODULE_V1_PADDING 0,0,0,0,0,0,0,0
  110. */
  111. uintptr_t spare_hook0;
  112. uintptr_t spare_hook1;
  113. uintptr_t spare_hook2;
  114. uintptr_t spare_hook3;
  115. uintptr_t spare_hook4;
  116. uintptr_t spare_hook5;
  117. uintptr_t spare_hook6;
  118. uintptr_t spare_hook7;
  119. };

所以在定义一个HTTP模块的时候,务必把type字段设置为NGX_HTTP_MODULE.

对于下列回调方法:init_module、init_process、exit_process、exit_master
调用他们的是Nginx框架代码。换句话说,这4个回调方法与HTTP框架无关。
即使nginx.conf中没有设置http{…}这种开启HTTP功能的配置项,这些
回调方法仍然会被调用。因此,通常开发HTTP模块时候都把他们设置为NULL。
这样Nginx不作为web服务器使用时,不会执行HTTP模块的任何代码。

定义HTTP时候,最重要的是要设置ctx和commands这两个成员。
对于HTTP类型模块来说,ngxmodule中的ctx指针必须指向ngx_http_module_t接口。

所以最终我们定义个ngx_module_t 模块如下:

  1. ngx_module_t ngx_http_mytest_module =
  2. {
  3. NGX_MODULE_V1,
  4. &ngx_http_mytest_module_ctx, /* module context */
  5. ngx_http_mytest_commands, /* module directives */
  6. NGX_HTTP_MODULE, /* module type */
  7. NULL, /* init master */
  8. NULL, /* init module */
  9. NULL, /* init process */
  10. NULL, /* init thread */
  11. NULL, /* exit thread */
  12. NULL, /* exit process */
  13. NULL, /* exit master */
  14. NGX_MODULE_V1_PADDING
  15. };

这样, mytest 模块在编译的时候, 就可以被加入到ngx_modules的全局数组中了。

7.6 完整代码 ngx_http_mytest_module.c

所以全部的编码工作完毕,最终的代码应该如下:

  1. #include <ngx_config.h>
  2. #include <ngx_core.h>
  3. #include <ngx_http.h>
  4. #include <sys/types.h>
  5. #include <unistd.h>
  6. //定义处理用户请求hello world handler
  7. static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
  8. {
  9. //必须是GET或者HEAD方法,否则返回405 Not Allowed
  10. if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD)))
  11. {
  12. return NGX_HTTP_NOT_ALLOWED;
  13. }
  14. //丢弃请求中的包体
  15. ngx_int_t rc = ngx_http_discard_request_body(r);
  16. if (rc != NGX_OK)
  17. {
  18. return rc;
  19. }
  20. //设置返回的Content-Type。注意,ngx_str_t有一个很方便的初始化宏
  21. //ngx_string,它可以把ngx_str_t的data和len成员都设置好
  22. ngx_str_t type = ngx_string("text/plain");
  23. //返回的包体内容
  24. ngx_str_t response = ngx_string("Hello World!");
  25. //设置返回状态码
  26. r->headers_out.status = NGX_HTTP_OK;
  27. //响应包是有包体内容的,所以需要设置Content-Length长度
  28. r->headers_out.content_length_n = response.len;
  29. //设置Content-Type
  30. r->headers_out.content_type = type;
  31. //发送http头部
  32. rc = ngx_http_send_header(r);
  33. if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
  34. {
  35. return rc;
  36. }
  37. //构造ngx_buf_t结构准备发送包体
  38. ngx_buf_t *b;
  39. b = ngx_create_temp_buf(r->pool, response.len);
  40. if (b == NULL)
  41. {
  42. return NGX_HTTP_INTERNAL_SERVER_ERROR;
  43. }
  44. //将Hello World拷贝到ngx_buf_t指向的内存中
  45. ngx_memcpy(b->pos, response.data, response.len);
  46. //注意,一定要设置好last指针
  47. b->last = b->pos + response.len;
  48. //声明这是最后一块缓冲区
  49. b->last_buf = 1;
  50. //构造发送时的ngx_chain_t结构体
  51. ngx_chain_t out;
  52. //赋值ngx_buf_t
  53. out.buf = b;
  54. //设置next为NULL
  55. out.next = NULL;
  56. //最后一步发送包体,http框架会调用ngx_http_finalize_request方法
  57. //结束请求
  58. return ngx_http_output_filter(r, &out);
  59. }
  60. //定义command用于处理配置项参数的set方法
  61. static char *
  62. ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  63. {
  64. ngx_http_core_loc_conf_t *clcf;
  65. //首先找到mytest配置项所属的配置块,clcf貌似是location块内的数据
  66. //结构,其实不然,它可以是main、srv或者loc级别配置项,也就是说在每个
  67. //http{}和server{}内也都有一个ngx_http_core_loc_conf_t结构体
  68. clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
  69. //http框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时,如果
  70. //请求的主机域名、URI与mytest配置项所在的配置块相匹配,就将调用我们
  71. //实现的ngx_http_mytest_handler方法处理这个请求
  72. clcf->handler = ngx_http_mytest_handler;
  73. return NGX_CONF_OK;
  74. }
  75. //定义ngx mytest 配置匹配的command
  76. static ngx_command_t ngx_http_mytest_commands[] =
  77. {
  78. {
  79. ngx_string("mytest"),
  80. NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_NOARGS,
  81. ngx_http_mytest,
  82. NGX_HTTP_LOC_CONF_OFFSET,
  83. 0,
  84. NULL
  85. },
  86. ngx_null_command
  87. };
  88. //定义ngx_http_module_t接口
  89. static ngx_http_module_t ngx_http_mytest_module_ctx =
  90. {
  91. NULL, /* preconfiguration */
  92. NULL, /* postconfiguration */
  93. NULL, /* create main configuration */
  94. NULL, /* init main configuration */
  95. NULL, /* create server configuration */
  96. NULL, /* merge server configuration */
  97. NULL, /* create location configuration */
  98. NULL /* merge location configuration */
  99. };
  100. //定义mytest模块
  101. ngx_module_t ngx_http_mytest_module =
  102. {
  103. NGX_MODULE_V1,
  104. &ngx_http_mytest_module_ctx, /* module context */
  105. ngx_http_mytest_commands, /* module directives */
  106. NGX_HTTP_MODULE, /* module type */
  107. NULL, /* init master */
  108. NULL, /* init module */
  109. NULL, /* init process */
  110. NULL, /* init thread */
  111. NULL, /* exit thread */
  112. NULL, /* exit process */
  113. NULL, /* exit master */
  114. NGX_MODULE_V1_PADDING
  115. };

7.7 配置文件 config

但是之后我们还需要给该模块相同目录下提供一个配置文件”config”表示在nginx编译的时候的一些编译选项。

  1. ngx_addon_name=ngx_http_mytest_module
  2. HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
  3. NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"

所以我们的模块编写完毕了,当前目录应该有两个文件

  1. ls
  2. config ngx_http_mytest_module.c

7.8 重新编译Nginx 并添加自定义模块

进入Nginx源码目录
执行

  1. ./configure --prefix=/usr/local/nginx --add-module=/home/ace/openSource_test/nginx_module_http_test

—add-module为刚才自定义模块源码的目录

  1. make
  2. sudo make install

测试自定义模块

修改nginx.conf文件

  1. server {
  2. listen 8777;
  3. server_name localhost;
  4. location / {
  5. mytest;#我们自定义模块的名称
  6. }
  7. }

重新启动nginx

打开浏览器输入地址和端口

打开浏览器输入地址和端口