Nginx模块开发:handler模块实现

简介: Nginx模块开发:handler模块实现

一、模块需要实现的功能

实现功能:统计每个ip地址访问的次数,并显示相应的html。(这个功能有什么用呢?可以根据统计次数,可以后续做成一个黑白名单,对访问次数较多的,禁止访问)

在conf文件中,可以设置count这条命令,就能够配置该模块。在访问/test资源的时候,就会对当前ip访问次数进行统计。

如下图所示,访问的是102.168.232.137的/test的资源,跳转到192.168.232.1,每访问一次count都会+1

访问不同的IP,对应的计数会重新开始。根据客户端的IP统计的

1、共享内存和slab

要使得每访问一次,计数加一,但浏览器访问的nginx服务器的worker进程可能不是同一个,因此可以使用共享内存的方式,进行进程间通信,访问的次数为共享资源。

然后可以通slab分配策略,对共享内存进行管理和分配。

shmem和slab是什么区别?

shmem是共享内存,slab是内存分配策略。因此对共享内存,可以采用slab的分配策略

2、存储的数据结构

可以看到下面显示了所有的ip以及它们对应的访问次数

如何实现上面的功能呢?

ip地址作为key,方位次数count作为value,通过红黑树将组织起来。每次访问的时候,只要让ip对应的count+1,如果ip所在的key,不在红黑树中,就创建一个新的节点,并赋值count=1。

显示上面的内容,只需要遍历红黑树,然后编码成html格式,发送出去就行了。

由于是在nginx端直接返回,并没有反向代理到服务器。因此是属于handler模块。

二、实现handler模块

下面自定义的一些函数按照执行顺序是

1.ngx_http_pagecount_create_location_conf 为结构体分配一块内存,用于传递参数

2.ngx_http_pagecount_set初始化一块共享内存(用于存储红黑树),并定义一个handler函数(执行count+1和返回html)

3.ngx_http_pagecount_init直接返回NGX_OK

tatic ngx_command_t count_commands[] = {
  {
    ngx_string("count"),//自定义的http模块指令为count
    NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,//标志位:指令配置位置为http{location{...}},并且该命令无需参数
    ngx_http_pagecount_set,//解析count命令时候的执行函数(在解析conf文件时候,遇到count命令就会执行它)
    NGX_HTTP_LOC_CONF_OFFSET,//指令存储地址。通过查找指令地址的函数去获得,指令的存储位置在http{location{..}}
    0, NULL
  },
  ngx_null_command//指令数组结束标志
};
static ngx_http_module_t count_ctx = {
  NULL,
  ngx_http_pagecount_init,//解析完conf要做的事,也就是可以去初始化http_pagecount
  NULL,
  NULL,
  NULL,
  NULL,
  ngx_http_pagecount_create_location_conf,//创建一块内存,放置loc的配置信息
  NULL,
};
//ngx_http_count_module 
ngx_module_t ngx_http_pagecount_module = {  //模块的名字一定要是唯一的,不能与其他模块的名字冲突
  NGX_MODULE_V1,
  &count_ctx,//具体的模块http模块
  count_commands,//模块支持的指令
  NGX_HTTP_MODULE,//模块类型为http模块
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NGX_MODULE_V1_PADDING
};
typedef struct {
  int count; //count
} ngx_http_pagecount_node_t;

1、ngx_http_pagecount_create_location_conf

为定义的一块结构体,分配一块空间,该结构体是为了存储共享内存等信息

typedef struct
{
    ssize_t shmsize;
    ngx_slab_pool_t *shpool;
    ngx_http_pagecount_shm_t *sh;
} ngx_http_pagecount_conf_t;
void  *ngx_http_pagecount_create_location_conf(ngx_conf_t *cf) {
  ngx_http_pagecount_conf_t *conf;
  conf = ngx_palloc(cf->pool, sizeof(ngx_http_pagecount_conf_t));
  if (NULL == conf) {
    return NULL;
  }
  conf->shmsize = 0;
  ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, "ngx_http_pagecount_create_location_conf");
  // init conf data
  // ... 
  return conf;
}

2、ngx_http_pagecount_set

获取一块共享内存,并设置共享内存初始化函数,以及设置handler函数

在nginx启动的时候,就会进行共享内存的初始化

每访问一次,就会调用一次handler函数

static char *ngx_http_pagecount_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
  ngx_shm_zone_t *shm_zone;//共享内存中的一块区域(不是整个共享内存)
  ngx_str_t name = ngx_string("pagecount_slab_shm");
  ngx_http_pagecount_conf_t *mconf = (ngx_http_pagecount_conf_t*)conf;
  ngx_http_core_loc_conf_t *corecf;
  ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, "ngx_http_pagecount_set000");
  mconf->shmsize = 1024*1024;
  shm_zone = ngx_shared_memory_add(cf, &name, mconf->shmsize, &ngx_http_pagecount_module);//从共享区域拿一块出来(为什么这里叫add呢?是表示,对外面已用的,加了一块)
  if (NULL == shm_zone) {
    return NGX_CONF_ERROR;
  }
  shm_zone->init = ngx_http_pagecount_shm_init;//共享内存的初始化函数(这里把这块内存做成slab)
  shm_zone->data = mconf;
  corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
  corecf->handler = ngx_http_pagecount_handler;//每访问一次网页,就会执行一次该函数(也就是会计数)
  return NGX_CONF_OK;
}

1)ngx_http_pagecount_shm_init

初始化共享内存

这里主要是配置slab,以及使用slab分配的共享内存,初始化一颗红黑树

ngx_int_t ngx_http_pagecount_shm_init (ngx_shm_zone_t *zone, void *data) {
  ngx_http_pagecount_conf_t *conf;
  ngx_http_pagecount_conf_t *oconf = data;
  conf = (ngx_http_pagecount_conf_t*)zone->data;
  if (oconf) {
    conf->sh = oconf->sh;
    conf->shpool = oconf->shpool;
    return NGX_OK;
  }
  printf("ngx_http_pagecount_shm_init 0000\n");
  conf->shpool = (ngx_slab_pool_t*)zone->shm.addr;
  conf->sh = ngx_slab_alloc(conf->shpool, sizeof(ngx_http_pagecount_shm_t));//通过slab分配一块空间(也就是说conf->sh这块共享内存,在slab里面)
  if (conf->sh == NULL) {
    return NGX_ERROR;
  }
  conf->shpool->data = conf->sh;
  printf("ngx_http_pagecount_shm_init 1111\n");
  ngx_rbtree_init(&conf->sh->rbtree, &conf->sh->sentinel, //ngx中自带的红黑树初始化,需要自己去实现插入(如何去比较key,和插入定时器中的那部分是一样的,模仿一下就行)
    ngx_http_pagecount_rbtree_insert_value);
  return NGX_OK;
}

2)ngx_http_pagecount_handler

handler函数,在count命令下加载模块后,访问指定资源,就会调用该函数

主要是对该ip对应的访问的次数+1,并遍历红黑树,以htmp编码的方式得到所有ip以及对应次数

//handler是接收到请求直接返回结构,不用像
static ngx_int_t ngx_http_pagecount_handler(ngx_http_request_t *r) {
  u_char html[1024] = {0};//最好用内存池去分配.
  int len = sizeof(html);
  ngx_rbtree_key_t key = 0;
  struct sockaddr_in *client_addr =  (struct sockaddr_in*)r->connection->sockaddr;//获取客户端的地址
  ngx_http_pagecount_conf_t *conf = ngx_http_get_module_loc_conf(r, ngx_http_pagecount_module);
  key = (ngx_rbtree_key_t)client_addr->sin_addr.s_addr;//ip地址
  ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno, " ngx_http_pagecount_handler --> %x\n", key);
  ngx_shmtx_lock(&conf->shpool->mutex);//临界资源需要加锁
  ngx_http_pagecount_lookup(r, conf, key);//如果查找到对应ip(key),就让它的访问次数(value)++,如果没有查找到,就插入一个新的节点,访问次数为1 
  ngx_shmtx_unlock(&conf->shpool->mutex);
  ngx_encode_http_page_rb(conf, (char*)html);//遍历红黑树,生成html的字符串格式
  //剩下就是对header和body进行编码
  //header
  r->headers_out.status = 200;
  ngx_str_set(&r->headers_out.content_type, "text/html");
  ngx_http_send_header(r);
  //body
  ngx_buf_t *b = ngx_pcalloc(r->pool,  sizeof(ngx_buf_t));
  ngx_chain_t out;
  out.buf = b;
  out.next = NULL;
  b->pos = html;
  b->last = html+len;
  b->memory = 1;
  b->last_buf = 1;
  return ngx_http_output_filter(r, &out);
}

3)对红黑树的一些操作

以下是红黑树的一些操作,如插入节点、查找节点、遍历红黑树

static void
ngx_http_pagecount_rbtree_insert_value(ngx_rbtree_node_t *temp,
        ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
   ngx_rbtree_node_t **p;
   //ngx_http_testslab_node_t *lrn, *lrnt;
    for (;;)
    {
        if (node->key < temp->key)
        {
            p = &temp->left;
        }
        else if (node->key > temp->key) {
            p = &temp->right;
        }
        else
        {
            return ;
        }
        if (*p == sentinel)
        {
            break;
        }
        temp = *p;
    }
    *p = node;
    node->parent = temp;
    node->left = sentinel;
    node->right = sentinel;
    ngx_rbt_red(node);
}
static ngx_int_t ngx_http_pagecount_lookup(ngx_http_request_t *r, ngx_http_pagecount_conf_t *conf, ngx_uint_t key) {
  ngx_rbtree_node_t *node, *sentinel;
  node = conf->sh->rbtree.root;
  sentinel = conf->sh->rbtree.sentinel;
  ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno, " ngx_http_pagecount_lookup 111 --> %x\n", key);
  //查找节点,如果找到就让它key(ip)对应的的value(次数)++
  while (node != sentinel) {
    if (key < node->key) {
      node = node->left;
      continue;
    } else if (key > node->key) {
      node = node->right;
      continue;
    } else { // key == node
      node->data ++;
      return NGX_OK;
    }
  }
  ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno, " ngx_http_pagecount_lookup 222 --> %x\n", key);
  //如果没有找到节点,就插入一个节点,让value(次数)=1
  // insert rbtree
  node = ngx_slab_alloc_locked(conf->shpool, sizeof(ngx_rbtree_node_t));//通过slab分配到共享内存中
  if (NULL == node) {
    return NGX_ERROR;
  }
  node->key = key;
  node->data = 1;
  ngx_rbtree_insert(&conf->sh->rbtree, node);
  ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno, " insert success\n");
  return NGX_OK;
}
static int ngx_encode_http_page_rb(ngx_http_pagecount_conf_t *conf, char *html) {
  sprintf(html, "<h1>Source Insight </h1>");
  strcat(html, "<h2>");
  //ngx_rbtree_traversal(&ngx_pv_tree, ngx_pv_tree.root, ngx_http_count_rbtree_iterator, html);
  //遍历节点
  ngx_rbtree_node_t *node = ngx_rbtree_min(conf->sh->rbtree.root, conf->sh->rbtree.sentinel);//查找左下角的节点
  do {
    char str[INET_ADDRSTRLEN] = {0};
    char buffer[128] = {0};
    sprintf(buffer, "req from : %s, count: %d <br/>",
      inet_ntop(AF_INET, &node->key, str, sizeof(str)), node->data);//把int类型的key转换成ip地址
    strcat(html, buffer);//字符连接
    node = ngx_rbtree_next(&conf->sh->rbtree, node);//查找下一个节点
  } while (node);
  strcat(html, "</h2>");
  return NGX_OK;
}

3、ngx_http_pagecount_init

空实现,直接返回一个NGX_OK

ngx_int_t   ngx_http_pagecount_init(ngx_conf_t *cf) {
#if 0
  ngx_http_core_loc_conf_t *cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
  ngx_http_handler_pt *h = ngx_array_push(cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
  if (NULL == h) {
    return NGX_ERROR;
  }
  *h = ngx_http_pagecount_handler;
#elif 0
  ngx_log_error(NGX_LOG_NOTICE, cf->log, ngx_errno, "ngx_http_pagecount_init");
  ngx_http_core_loc_conf_t *corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
  corecf->handler = ngx_http_pagecount_handler;
  //return NGX_OK;
#endif
  return NGX_OK;
}

相关文章
|
1月前
|
应用服务中间件 nginx
Nginx安装nginx-rtmp-module模块
【2月更文挑战第4天】 nginx中的模块虽然就是类似插件的概念,但是它无法像VsCode那样轻松的安装扩展。 nginx要安装其它模块必须同时拿到nginx源代码和模块源代码,然后手动编译,将模块打到nginx中,最终生成一个名为nginx的可执行文件。
67 6
|
3月前
|
应用服务中间件 nginx
百度搜索:蓝易云【利用nginx内置ngx_http_mirror_module模块实现流量复制及流量放大】
以上就是使用Nginx内置 `ngx_http_mirror_module`模块实现流量复制和流量放大的简要示例。通过合理配置和利用该模块,可以实现更复杂的流量控制和调试需求。
53 1
|
2月前
|
消息中间件 关系型数据库 MySQL
使用Nginx的stream模块实现MySQL反向代理与RabbitMQ负载均衡
使用Nginx的stream模块实现MySQL反向代理与RabbitMQ负载均衡
56 0
|
3月前
|
存储 应用服务中间件 nginx
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现
58 0
|
3月前
|
存储 应用服务中间件 nginx
Nginx:过滤模块的实现
Nginx:过滤模块的实现
|
3月前
|
存储 负载均衡 网络协议
Nginx: handler 模块的实现
Nginx: handler 模块的实现
|
9月前
|
应用服务中间件 Linux 网络安全
虚拟机Centos下载安装Nginx并安装ssl模块——小白教程
虚拟机Centos下载安装Nginx并安装ssl模块——小白教程
253 0
|
应用服务中间件 开发工具 nginx
Mac通过Brew安装Nginx的Echo模块
Mac通过Brew安装Nginx的Echo模块
627 0
|
应用服务中间件 nginx
安装好的nginx安装新的模块
nginx动态加载模块。
1359 0
|
应用服务中间件 nginx