• Nginx 模块细节详探


      本文主要基于

      http://www.codinglabs.org/html/intro-of-nginx-module-development.html

      http://www.evanmiller.org/nginx-modules-guide.html#compiling

      的学习些的

      nginx模块要负责三种角色

      handler:接收请求+产生Output

      filters:处理hander产生的output

      load-balancer:负载均衡,选择一个后端server发送请求(如果把nginx当做负载均衡服务器的话,这个角色必须实现)


      nginx内部流程(非常重要)

      图片讲解:

      clip_image001

      英文讲解

      Client sends HTTP request → Nginx chooses the appropriate handler based on the location config → (if applicable) load-balancer picks a backend server → Handler does its thing and passes each output buffer to the first filter → First filter passes the output to the second filter → second to third → third to fourth → etc. → Final response sent to client

      中文讲解:

      客户端发送http请求 -- nginx根据配置文件conf中的location来确定由哪个handler处理-- handler执行完request返回output给filter--第一个filter处理output -- 第二个filter处理output--- … -- 生成Response


      Nginx模块的几个数据结构

      1 Module Configuration Struct(s) 模块配置结构

      2 Module Directives 模块命令结构

      3 The Module Context模块内容

      3.1 create_loc_conf

      3.2 merge_loc_conf

      4 The Module Definition模块整合

      5 Module Installation模块安装

      1 模块配置结构:

      这个结构的命名规则为ngx_http_[module-name]_[main|srv|loc]_conf_t。

      main,srv,loc表示这个模块的作用范围是配置文件中的main/server/location三种范围(这个需要记住,后面会经常用到)

      例子:

      typedef struct {
      
      ngx_str_t ed; //echo模块只有一个参数 比如 echo "hello"
      
      } ngx_http_echo_loc_conf_t; //echo 模块

      2 模块命令结构:
      例子:

      static ngx_command_t ngx_http_echo_commands[] = {
      
      { ngx_string("echo"), //命令名字
      
      NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, //代表是local配置,带一个参数
      
      ngx_http_echo, //组装模块配置结构
      
      NGX_HTTP_LOC_CONF_OFFSET, //上面的组装模块配置获取完参数后存放到哪里?使用这个和下面的offset参数来进行定位
      
      offsetof(ngx_http_echo_loc_conf_t, ed), //同上
      
      NULL // Finally, post is just a pointer to other crap the module might need while it's reading the configuration. It's often NULL.我也没理解是什么意思。通常情况下设置为NULL
      
      },
      
      ngx_null_command //必须使用ngx_null_command作为commands的结束标记
      
      };
      注1:

      ngx_http_echo 是组装模块配置结构的函数指针,有三个参数:

      ngx_conf_t *cf 包含这个命令的所有参数

      ngx_command_t *cmd 执行这个command命令结构的指针

      void *conf 模块订制的配置结构

      这个函数比较不好理解,其功能是把参数传到命令结构体中,并且把合适的值放入到模块配置结构中。我们称之为"setup function"。它会在命令运行的时候被调用。

      nginx已经提供了几个现成的方法了:

      ngx_conf_set_flag_slot

      ngx_conf_set_str_slot

      ngx_conf_set_num_slot

      ngx_conf_set_size_slot

      所以你可以这样定义cmd:

      { ngx_string("add_after_body"),
      
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      
      ngx_conf_set_str_slot,
      
      NGX_HTTP_LOC_CONF_OFFSET,
      
      offsetof(ngx_http_addition_conf_t, after_body),
      
      NULL },
      也可以这样:

      static ngx_command_t ngx_http_echo_commands[] = {
      
      { ngx_string("echo"),
      
      NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      
      ngx_http_echo,
      
      NGX_HTTP_LOC_CONF_OFFSET,
      
      offsetof(ngx_http_echo_loc_conf_t, ed),
      
      NULL },
      
      ngx_null_command
      
      };
      
      static char *
      
      ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
      
      {
      
      ngx_http_core_loc_conf_t *clcf;
      
      clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
      
      clcf->handler = ngx_http_echo_handler; //这里指定了handler,那么就会使用新的handler进行处理
      
      ngx_conf_set_str_slot(cf,cmd,conf); //这里还是使用系统的函数
      
      return NGX_CONF_OK;
      
      }

      3 模块内容

      static ngx_http_module_t ngx_http_circle_gif_module_ctx = {

      NULL, /* preconfiguration */

      NULL, /* postconfiguration 这里是放置filter的地方,在filter章节会说*/

      NULL, /* create main configuration */

      NULL, /* init main configuration */

      NULL, /* create server configuration */

      NULL, /* merge server configuration */

      ngx_http_circle_gif_create_loc_conf, /* create location configuration */

      ngx_http_circle_gif_merge_loc_conf /* merge location configuration */

      };

      模块的内容ngx_http_<module name>_module_ctx是为了定义各种钩子函数,就是nginx在各个不同的时期将会运行的函数。

      一般的location只需要配置create location configuration(在创建location配置的时候运行)和merge location configuration(和server config如何合并,一般包含如果配置有错误的话应该抛出异常)

      这两个函数的例子:(来自https://github.com/evanmiller/nginx_circle_gif/

      static void *

      ngx_http_circle_gif_create_loc_conf(ngx_conf_t *cf)

      {

      ngx_http_circle_gif_loc_conf_t *conf;

      conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_circle_gif_loc_conf_t));

      if (conf == NULL) {

      return NGX_CONF_ERROR;

      }

      conf->min_radius = NGX_CONF_UNSET_UINT; //对conf中的每个参数进行配置,min_redius和max_redius是nginx_circle_gif模块的配置结构的字段

      conf->max_radius = NGX_CONF_UNSET_UINT;

      return conf;

      }

      static char *

      ngx_http_circle_gif_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)

      {

      ngx_http_circle_gif_loc_conf_t *prev = parent; //server的loc配置

      ngx_http_circle_gif_loc_conf_t *conf = child; // 自己的loca配置

      ngx_conf_merge_uint_value(conf->min_radius, prev->min_radius, 10);

      ngx_conf_merge_uint_value(conf->max_radius, prev->max_radius, 20);

      if (conf->min_radius < 1) {

      ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,

      "min_radius must be equal or more than 1");

      return NGX_CONF_ERROR; //这里负责抛出错误

      }

      if (conf->max_radius < conf->min_radius) {

      ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,

      "max_radius must be equal or more than min_radius");

      return NGX_CONF_ERROR; //这里负责抛出错误

      }

      return NGX_CONF_OK;

      }

      注1 : ngx_conf_merge_uint_value是nginx core中自带的函数

      ngx_conf_merge_<data type>_value

      ngx_conf_merge_uint_value(conf->min_radius, prev->min_radius, 10);的意思是:

      如果设置了conf->min_redius的话使用conf->min_redius

      如果没有设置conf->min_redius的话使用 prev->min_radius

      如果两个都没有设置的话使用10

      更多函数请看 core/ngx_conf_file.h

      4 模块整合

      ngx_module_t ngx_http_<module name>_module = {

      NGX_MODULE_V1,

      &ngx_http_<module name>_module_ctx, /* module context 模块内容 */

      ngx_http_<module name>_commands, /* module directives 模块命令*/

      NGX_HTTP_MODULE, /* module type 模块类型,HTTP模块,或者HTTPS*/

      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

      };

      5 模块安装

      模块安装文件的编写依赖于这个模块是handler,filter还是load-balancer的工作角色

      下面开始是Handler,filter,load-balancer的编写和安装

      1 Handler安装

      还记得在模块命令的时候有设置handler的语句吗?

      clcf->handler = ngx_http_echo_handler;

      这个语句就是handler的安装

      2 Handler编写

      Handler的执行有四部:

      读入模块配置

      处理功能业务

      产生HTTP header

      产生HTTP body

      读入模块配置

      例子:

      static ngx_int_t
      
      ngx_http_circle_gif_handler(ngx_http_request_t *r)
      
      {
      
      ngx_http_circle_gif_loc_conf_t *circle_gif_config;
      
      circle_gif_config = ngx_http_get_module_loc_conf(r, ngx_http_circle_gif_module);
      
      ...

      就是使用nginx已经有的函数ngx_http_get_module_loc_conf,第一个参数是当前请求,第二个参数是前面写好的模块

      处理功能业务

      这个部分是我们要模块处理的实际部分

      用需求举例:

      这个模块有个命令是 getRedisInfo 192.168.0.1 //获取redis的信息

      那么这个功能业务就是(伪代码):

      case cmd->opcode

      {

      "getRedisInfo" :

      获取redis 的信息

      }

      这里应该把所有这个模块设置的命令的业务逻辑都写好

      产生HTTP Header

      例子:

      r->headers_out.status = NGX_HTTP_OK;

      r->headers_out.content_length_n = 100;

      r->headers_out.content_type.len = sizeof("image/gif") - 1;

      r->headers_out.content_type.data = (u_char *) "image/gif";

      ngx_http_send_header(r);

      产生HTTP Body

      这个部分是最重要的一步

      借用codingLabs的图讲解一下nginx的IO

      clip_image002

      handler是可以一次产生出一个输出,也可以产生出多个输出使用ngx_chain_t的链表来进行连接

      struct ngx_chain_s {

      ngx_buf_t *buf;

      ngx_chain_t *next;

      };

      buf中有pos和last来代表out数据在内存中的位置,next是代表下一个ngx_chain_t

      下面来说一下只有一个ngx_chain_t的设置

      1 申明

      ngx_buf_t *b;

      ngx_chain_t out

      2 设置buffer

      b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));

      if (b == NULL) {

      ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,

      "Failed to allocate response buffer.");

      return NGX_HTTP_INTERNAL_SERVER_ERROR;

      }

      b->pos = some_bytes; /* first position in memory of the data */

      b->last = some_bytes + some_bytes_length; /* last position */

      b->memory = 1; /* content is in read-only memory */

      /* (i.e., filters should copy it rather than rewrite in place) */

      b->last_buf = 1; /* there will be no more buffers in the request */

      3 模块加入链表

      out.buf = b;

      out.next = NULL; //如果有下一个链表可以放到这里

      4 返回

      return ngx_http_output_filter(r, &out);

      2 Filter的编写和安装

      Filter作为过滤器又可以细分为两个过滤器: Header filters 和 body filters

      Filter的安装

      filter是在模块内容设置的时候加上的

      例子:

      static ngx_http_module_t ngx_http_chunked_filter_module_ctx = {

      NULL, /* preconfiguration */

      ngx_http_chunked_filter_init, /* postconfiguration */

      ...

      };

      static ngx_int_t

      ngx_http_chunked_filter_init(ngx_conf_t *cf)

      {

      ngx_http_next_header_filter = ngx_http_top_header_filter;

      ngx_http_top_header_filter = ngx_http_chunked_header_filter;

      ngx_http_next_body_filter = ngx_http_top_body_filter;

      ngx_http_top_body_filter = ngx_http_chunked_body_filter;

      return NGX_OK;

      }

      注1: ngx_http_top_hreader_filter是什么意思呢?

      当handler生成了response的时候,它调用了两个方法:ngx_http_output_filter和ngx_http_send_header

      ngx_http_output_filter会调用ngx_http_top_body_filter

      ngx_http_send_header会调用ngx_top_header_filter

      Filter的编写

      Header filters

      分为三个部分:

      是否操作这个handler的response

      操作response

      调用下一个filter

      例子

      static
      
      ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r)
      
      {
      
      time_t if_modified_since;
      
      if_modified_since = ngx_http_parse_time(r->headers_in.if_modified_since->value.data,
      
      r->headers_in.if_modified_since->value.len);
      
      /* step 1: decide whether to operate */
      
      if (if_modified_since != NGX_ERROR && 
      
      if_modified_since == r->headers_out.last_modified_time) {
      
      /* step 2: operate on the header */
      
      r->headers_out.status = NGX_HTTP_NOT_MODIFIED; //返回304
      
      r->headers_out.content_type.len = 0; //长度设置为0
      
      ngx_http_clear_content_length(r); //清空
      
      ngx_http_clear_accept_ranges(r); //清空
      
      }
      
      /* step 3: call the next filter */
      
      return ngx_http_next_header_filter(r);
      
      }

      Body filters

      假设有个需求:在每个request后面插入"<l!-- Served by Nginx -->"

      1 要找出最后chain的最后一个buf

      ngx_chain_t *chain_link;
      
      int chain_contains_last_buffer = 0;
      
      for ( chain_link = in; chain_link != NULL; chain_link = chain_link->next ) {
      
      if (chain_link->buf->last_buf)
      
      chain_contains_last_buffer = 1;
      
      }

      2 创建一个新的buf

      ngx_buf_t *b;
      
      b = ngx_calloc_buf(r->pool);
      
      if (b == NULL) {
      
      return NGX_ERROR;
      
      }
      3 放数据在新buf上

      b->pos = (u_char *) "<!-- Served by Nginx -->";
      
      b->last = b->pos + sizeof("<!-- Served by Nginx -->") - 1;
      4 把新buf放入一个新chain_t

      ngx_chain_t *added_link;
      
      added_link = ngx_alloc_chain_link(r->pool);
      
      if (added_link == NULL)
      
      return NGX_ERROR;
      
      added_link->buf = b;
      
      added_link->next = NULL;
      5 把新的chain链接到原来的chain_link中

      chain_link->next = added_link;

      6 重新设置last_buf

      chain_link->buf->last_buf = 0;

      added_link->buf->last_buf = 1;

      7 传给下一个filter

      return ngx_http_next_body_filter(r, in);

      最后一点是如何写和编译nginx模块

      必须写两个文件configngx_http_<your module>_module.c

      其中config会被./configure包含

      ngx_addon_name=ngx_http_<your module>_module

      HTTP_MODULES="$HTTP_MODULES ngx_http_<your module>_module"

      NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_<your module>_module.c"

      几乎都是填空

      ngx_http_<your module>_module.c文件就是你的所有模块代码

      编译nginx:

      ./configure --add-module=path/to/your/new/module/directory #这里是config放置的地方

      ----------------------

      作者:yjf512(轩脉刃)

      出处:http://www.cnblogs.com/yjf512/

      本文版权归yjf512和cnBlog共有,欢迎转载,但未经作者同意必须保留此段声明

    实时了解作者更多技术文章,技术心得,请关注微信公众号“轩脉刃的刀光剑影”

    本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载,演绎,但是必须保留本文的署名叶剑峰(包含链接http://www.cnblogs.com/yjf512/),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系

  • 相关阅读:
    Linux 的文件软链接如何删除
    mysql-xtrabackup备份sh: xtrabackup_56: command not found与error while loading shared libraries: libssl.so.6: cannot open shared object file: No such file or directory
    How To Upgrade ASMLib Kernel Driver as Part of Kernel Upgrade? (文档 ID 1391807.1)
    [trouble] error connecting to master 'repl@192.168.1.107:3306'
    Troubleshooting 10g and 11.1 Clusterware Reboots (文档 ID 265769.1)
    "Last_IO_Error: Fatal error: The slave I/O thread stops because master and slave have equal MySQL server UUIDs
    RMAN-06900 RMAN-06901 ORA-19921
    一则ORACLE进程都在但是无法进入实例的问题
    VirtualBox下Win7下CPU高占用的一次故障解决
    netcore之mysql中文乱码问题解决记录
  • 原文地址:https://www.cnblogs.com/yjf512/p/2428385.html
Copyright © 2020-2023  润新知