nginx 模块开发复习笔记

nginx模块开发?

大部分情况下我们对于nginx的使用包括负载均衡、流量切换,使用nginx本身灵活的配置文件也基本上都能满足需求,但是在某些情况下就不太足够了,比如说对于ip快速的封禁&解封,当然了使用nginx本身的deny指令在某种情况下也能实现这种效果,但是需要对nginx进行频繁的reload,在某种程度上并不是最优解,这种情况下就需要nginx有一个模块他能支持对于nginx访问ip快速便捷的修改并且保证原本服务不受影响:)

这里说一句题外话,以我现有的经验和理解,在我看来programmer+computer的组合目标是在于将信息以高效的方式进行处理、传播与整合,互联网行业不同的职位则是从不同层面去处理信息,例如从应用层面处理信息最典型的例如http服务,细粒度的就是代码级别对信息的处理,例如各个公司程序员做的事情,当然了这中间还有一层就是使用shell来处理信息例如grep、awk等,从这种角度来看这个需求的话nginx实现对于ip的封禁需求确实从代码层面去做逻辑控制是最优的。

nginx以其高效、灵活闻名于世,相应的其优点是建立在其优雅&高度模块化&良好设计的代码之上,因此nginx模块开发上手还是比较容易的。

简单介绍

这里先推荐两个站点,对于入门以及平时开发十分有帮助:

这里主要说一下nginx对于lib的封装简直到了神一般的境界,甚至可以说开发nginx模块使用的是nginx-c语言了。。。

首先对于字符串的处理,nginx是这样处理的:

1
2
3
4
5
#定义了一个结构体用来存储字符串的起始指针地址,以及该字符串的长度
typedef struct{
size_t len;
u_char *data;
}ngx_str_t;

而这样定义的原因是:在数据的处理中频繁的对字符串进行复制,处理传输,涉及到的malloc会大大影响性能(考虑一下c++ string),因此使用指针+长度这种方式来对字符串进行处理、复制其代价只是相当于操作一个整形变量。膜拜

nginx的设计可见一斑!

相应的nginx还有自己的buff类型pool类型,有兴趣的同学可以自己看一下文档。

代码结构

nginx本身相当于是一个http工厂,我们开发一个新的模块相当于是把一个机器加到了nginx的流水线上,模块应该是有7(具体数字记不清楚了)个注册点,在代码中指定该模块在那一层调用,然后在模块的内部定义中注册希望调用的函数,这个函数可以看作是我们代码逻辑的入口,相应的还有读取配置这一层逻辑,整理一下就是:

  • 模块本身的定义
    • 包括模块版本
    • 模块的上下文信息
      • 注册处理配置文件的函数
      • 注册处理请求的函数
    • 模块需要读取的配置文件信息

下面以一段hello world为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#include<ngx_config.h>
#include<ngx_core.h>
#include<ngx_http.h>
#include<stdio.h>
#include<string.h>

typedef struct
{
ngx_str_t output_words;
} ngx_http_hello_world_loc_conf_t;

static char* ngx_http_hello_world(ngx_conf_t* cf,ngx_command_t* cmd,void* conf);
static void* ngx_http_hello_world_create_loc_conf(ngx_conf_t* cf);
//static char* ngx_http_hello_world_merge_loc_conf(ngx_conf_t* cf,void* parent,void* child);

static ngx_int_t ngx_http_hello_world_handler(ngx_http_request_t* r);
/*
下面的数组主要定义该模块读取的配置项,以及该配置项允许出现在的配置块,例如location?server、还是http
*/
static ngx_command_t ngx_http_hello_world_commands[] = {
{
ngx_string("hello_world"),
NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS | NGX_CONF_TAKE1,
ngx_http_hello_world,
//可以出现在http server块里面的location配置指令里
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_hello_world_loc_conf_t,output_words),
NULL
},
ngx_null_command
};

/*模块上下文
下面这段代码注册了如何读取配置项的函数代码
*/
static ngx_http_module_t ngx_http_hello_world_module_ctx = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
ngx_http_hello_world_create_loc_conf,
NULL
//ngx_http_hello_world_merge_loc_conf
};


//模块内容Top
/*
模块的定义,注册了模块需要读取的配置项以及上下文信息
*/
ngx_module_t ngx_http_hello_world_module = {
NGX_MODULE_V1,
&ngx_http_hello_world_module_ctx,
ngx_http_hello_world_commands,
NGX_HTTP_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};

int display_ngx_str(ngx_str_t* ngx_str,char* tag_str)
{
char buff[1024];
snprintf(buff,ngx_str->len + 1,"%s",ngx_str->data);
printf("%s:%s\n",tag_str,buff);
//printf("%ld\n",ngx_str->len);
//printf("%s:%s\n",tag_str,ngx_str->data);
return 0;
}

//alloc memory for conf struct
static void* ngx_http_hello_world_create_loc_conf(ngx_conf_t* cf)
{
ngx_http_hello_world_loc_conf_t* conf;
conf = ngx_pcalloc(cf->pool,sizeof(ngx_http_hello_world_loc_conf_t));
if(conf == NULL)
{
return NGX_CONF_ERROR;
}
conf->output_words.len = 0;
conf->output_words.data = NULL;
printf("%s\n","In Hello_world create_loc_conf");
return conf;
}
/*
nginx的配置项有一个继承关系在,例如最外层的access_log是可以由location层进行继承的,所以nginx的配置文件读取这一块还是有点复杂的
*/
/*
以下定义的就是该模块对于nginx请求处理的函数,也是我们编写的主题了,这个比较简单,就是读取配置文件中hello_world后面定义的字符串并返回给页面
*/
static ngx_int_t ngx_http_hello_world_handler(ngx_http_request_t* r)
{
ngx_int_t rc;
ngx_buf_t* b;
ngx_chain_t out[2];
ngx_http_hello_world_loc_conf_t* hlcf;
//获取模块配置
hlcf = ngx_http_get_module_loc_conf(r,ngx_http_hello_world_module);
r->headers_out.content_type.len = sizeof("text/plain") - 1;
r->headers_out.content_type.data = (u_char*)"text/plain";

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

out[0].buf = b;
out[0].next = &out[1];

b->pos = (u_char*)"hello_world,";
b->last = b->pos + sizeof("hello_world,") - 1;
b->memory = 1;

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

out[1].buf = b;
out[1].next = NULL;

b->pos = hlcf->output_words.data;
b->last = hlcf->output_words.data + (hlcf->output_words.len);
b->memory = 1;
b->last_buf = 1;

r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = hlcf->output_words.len + sizeof("hello_world,") - 1;
rc = ngx_http_send_header(r);
if(rc == NGX_ERROR || rc > NGX_OK || r->header_only)
{
return rc;
}
printf("%s\n","In Hello_world_handler");
return ngx_http_output_filter(r,&out[0]);
}
/*
这里是相当于一些初始化操作,下面的handler=就是将上面定义的函数作为handler注册
*/
static char* ngx_http_hello_world(ngx_conf_t* cf,ngx_command_t* cmd,void* conf)
{
printf("%s\n","In Hello_world");
ngx_http_core_loc_conf_t* clcf;
clcf = ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module);
clcf->handler = ngx_http_hello_world_handler;
ngx_conf_set_str_slot(cf,cmd,conf);

return NGX_CONF_OK;
}

编译

以上代码写完之后,我们将代码命名为ngx_http_hello_world_module,在nginx源码文件夹src下面新建一个同名文件夹将代码放入,同时在ngx_http_hello_world_module文件夹下面创建一个config文件内容为:

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

做到这一步,我们的模块就准备完毕了,然后就是进行代码的编译了,这里我们需要通过再执行./configure的时候指定参数–add-module=src/ngx_http_hello_world_module 将我们的模块编译进去,完成之后make崭新的bin文件就诞生在obj文件夹下面了

运行

配置文件也要做少许修改

1
2
3
location /hello {
hello_world HHH;
}

调试

最后就是调试这一步了,这里有两点需要注意一下:

  1. 使用单独的配置文件
  2. 使用非daemon模式运行,便于查看输出信息

最终完整版配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

#user html;
worker_processes 0;
daemon off;
master_process off;

error_log logs/error.log;

events {
worker_connections 1024;
}


http {
#include /etc/nginx/conf.d/*.conf;
include mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log logs/access.log main;

sendfile on;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;

#gzip on;

server {
listen 80;

#charset koi8-r;

#access_log logs/host.access.log main;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}

location /hello {
hello_world HHH;
}
}
}

执行的命令如下:

1
./objs/nginx -p ./ -c conf/nginx.conf

至此一个最简单的nginx模块就出来了,后面涉及到复杂的模块开发还会涉及到gdb之类工具的使用,今天就先写这些:)

转载请注明来源链接 http://just4fun.im/2017/10/28/nginx-模块开发复习笔记/ 尊重知识,谢谢:)