模块的开发需要一定的代码架构和操作步骤。要符合主体代码要求
实例胜千言,所以我准备了这个代码供入门参考。
1. 准备模块代码
2. 编写配置文件
3. 运行./configure 编译nginx 程序
4. make & make install
5. 运行测试
甲: 前言
Nginx的模块动态添加,所有的模块都要预先编译进Nginx的二进制可执行文件中。
所以要./configure, make, make install
Nginx模块有3种角色:
1.Handlers(处理模块)----------用于处理Http请求并输出内容
2.Filters(过滤模块) ----------用于过滤Handlers输出的内容
3.Load-balancers(负载均衡模块) or upstream 模块---当多余一台的后端服务器供选择时,选择一台后端服务器并将Http请求转发到该服务器
Nginx模块的处理流程:
客户端发生Http请求到Nginx服务器 -> Nginx基于配置文件选择一个合适的处理模块
-> 负载均衡模块选择一个后端服务器 -> 处理模块并把输出缓冲放到第一个过滤模块上
-> 一直经过了N个过滤模块后 -> 把处理结果发送到客户端。
用一张图最好,不过文本也能说明问题。
乙:准备模块代码
1. #include <ngx_config.h>
2. #include <ngx_core.h>
3. #include <ngx_http.h>
4. #include <pthread.h>
5. #include <string.h>
6. #include <errno.h>
7.
8.
9. #define TIMING_BUF_BYTES (2 * 1024 * 1024)
10.
11. static
12. static void
13. static char* ngx_http_m3u9(ngx_conf_t* cf, ngx_command_t* cmd, void* conf);
14. static int read_big_block (int fd, unsigned char *buf, size_t
15. static int seek_to_pos (int
16.
17. static void* init_num_ram(void);
18. static int myrand(void);
19. static void
20.
21. // static u_char ngx_m3u9[] = "m3u9, hello, world";
22. static unsigned char
23. static int
24. static
25. static const char *dev_name_m3u9="/dev/sdc";
26. //static const char *dev_name_m3u9="/dev/md0";
27.
28.
29. static
30. {
31. {
32. "m3u9"),
33. NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
34. ngx_http_m3u9,
35. 0,
36. 0,
37. NULL
38. },
39. ngx_null_command
40. };
41.
42.
43. static
44. {
45. NULL,
46. NULL,
47. NULL,
48. NULL,
49. NULL,
50. NULL,
51. NULL,
52. NULL
53. };
54.
55. ngx_module_t ngx_http_m3u9_module =
56. {
57. NGX_MODULE_V1,
58. &ngx_http_m3u9_module_ctx,
59. ngx_http_m3u9_commands,
60. NGX_HTTP_MODULE,
61. NULL,
62. NULL,
63. &user_init_m3u9,
64. NULL,
65. NULL,
66. &user_uninit_m3u9,
67. NULL,
68. NGX_MODULE_V1_PADDING
69. };
70.
71.
72. static
73. {
74. char
75. if(!g_buf_m3u9)
76. {
77. "m3u9: g_buf_m3u9 malloc error");
78. return
79. }
80. pthread_mutex_init(&read_lock, NULL);
81.
82. g_fd_m3u9 = open (dev_name_m3u9, O_RDONLY);
83. if
84. "m3u9: g_fd_m3u9 open error, g_fd_m3u9:%d", g_fd_m3u9);
85. free(g_buf_m3u9);
86. g_buf_m3u9 = NULL;
87. g_fd_m3u9 = 0;
88. return
89. }
90. // srand(time(NULL));
91. // mysrand(time(NULL)) ;
92. init_num_ram();
93.
94. "m3u9: user_init_m3u9 called,devname:%s", dev_name_m3u9);
95. return
96. }
97.
98. void
99. {
100. if(g_buf_m3u9)
101. {
102. free(g_buf_m3u9);
103. g_buf_m3u9 = NULL;
104. }
105. if(g_fd_m3u9)
106. {
107. close(g_fd_m3u9);
108. g_fd_m3u9 = 0;
109. }
110.
111. "m3u9: user_uninit_m3u9 called");
112. }
113.
114. static
115. {
116. ngx_buf_t* b;
117. ngx_chain_t out;
118.
119. "m3u9: ngx_http_m3u9_handler called");
120.
121. sizeof("text/plain") - 1;
122. "text/plain";
123. request->headers_out.status = NGX_HTTP_OK;
124.
125. sizeof(ngx_buf_t));
126.
127. out.buf = b;
128. out.next = NULL;
129.
130. int
131. if
132. {
133. // memcpy(param, request->args.data, request->args.len);
134. }
135. else
136. {
137. // b->pos = ngx_m3u9;
138. // b->last = ngx_m3u9 + sizeof(ngx_m3u9);
139. // ret = sizeof(ngx_m3u9);
140. int
141. "m3u9: randnum:%d",randnum);
142.
143. off_t offset = (off_t) randnum * TIMING_BUF_BYTES;
144. // 1 T
145. pthread_mutex_lock(&read_lock);
146. if(seek_to_pos(g_fd_m3u9, offset))
147. {
148. "m3u9: seek error, offset:0x%lx",offset);
149. }
150. if(read_big_block(g_fd_m3u9, g_buf_m3u9, TIMING_BUF_BYTES))
151. {
152. "m3u9: read_big_block error");
153. }
154. pthread_mutex_unlock(&read_lock);
155. b->pos = g_buf_m3u9;
156. b->last = g_buf_m3u9 + TIMING_BUF_BYTES;
157. ret = TIMING_BUF_BYTES;
158.
159. }
160. b->memory = 1;
161. b->last_buf = 1;
162.
163. if
164. request->headers_out.status = NGX_HTTP_OK;
165. else
166. {
167. request->headers_out.status = NGX_HTTP_NOT_FOUND;
168. }
169.
170. request->headers_out.content_length_n = ret >= 0 ? ret : 0;
171. ngx_http_send_header(request);
172.
173. ngx_int_t retFilter = ngx_http_output_filter(request, &out);
174.
175. return
176. }
177.
178. static char* ngx_http_m3u9(ngx_conf_t* cf, ngx_command_t* cmd, void* conf)
179. {
180. ngx_http_core_loc_conf_t* pclcf = NULL;
181.
182. pclcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
183. pclcf->handler = ngx_http_m3u9_handler;
184.
185. "m3u9: handler installer called, devname:%s",dev_name_m3u9);
186. return
187. }
188.
189.
190. // TIMING_BUF_BYTES
191. static int read_big_block (int fd, unsigned char *buf, size_t
192. {
193. int
194. if(fd == -1) return
195. if
196. {
197. if
198. {
199. if
200. "m3u9: read() failed");
201. else
202. "m3u9: read(%u) returned %u bytes", TIMING_BUF_BYTES, rc);
203. }
204. else
205. {
206. "m3u9: read() hit EOF - device too small", stderr);
207. }
208. return
209. }
210. /* access all sectors of buf to ensure the read fully completed */
211. /*
212. for (i = 0; i < TIMING_BUF_BYTES; i += 512)
213. {
214. // buf[i] &= 1;
215. char randnum = rand() % 0xff;
216. buf[i]=randnum;
217. }
218. */
219. return
220. }
221.
222. static int seek_to_pos (int
223. {
224. if(fd== -1) return
225. "m3u9: fd:%d, pos:%ld",fd, pos); // shit, hex format not supported
226. if
227. "m3u9: lseek() failed");
228. return
229. }
230. return
231. }
232.
233. #define BUF_BYTES (512 * 1024)
234.
235. static char
236. static
237. static int
238. void* init_num_ram(void)
239. {
240. mysrand(time(NULL));
241. g_pRandRam=malloc(BUF_BYTES);
242. if(!g_pRandRam)
243. {
244. "error malloc\n");
245. return
246. }
247. "g_pRandRam: %p", g_pRandRam);
248. memset(g_pRandRam,0,BUF_BYTES);
249. return
250. }
251.
252. int myrand(void)
253. {
254. if(g_pRandRam == NULL)
255. {
256. "g_pRandRam: %p", g_pRandRam);
257. return
258. }
259.
260. g_num++;
261. if(g_num>=BUF_BYTES)
262. {
263. g_num = 0;
264. memset(g_pRandRam,0,BUF_BYTES);
265. }
266. g_next = g_next * 1103515245 + 123457;
267. int
268. while(1)
269. {
270. if(g_pRandRam[ret]==0)
271. {
272. g_pRandRam[ret]=1;
273. break;
274. }
275. else
276. {
277. ret++;
278. if(ret>=BUF_BYTES)
279. {
280. ret=0;
281. }
282. }
283. }
284.
285. return
286. }
287.
288. void
289. {
290. g_next = seed;
291. }
代码简要注释:
需要定义一个ngx_module_t 变量, 翻译为模块结构体变量
此处命名为ngx_http_m3u9_module
ngx_module_t ngx_http_m3u9_module =
{
NGX_MODULE_V1,
&ngx_http_m3u9_module_ctx,
ngx_http_m3u9_commands,
NGX_HTTP_MODULE,
NULL,
NULL,
&user_init_onprocess,
NULL,
NULL,
&user_uninit_onprocess,
NULL,
NGX_MODULE_V1_PADDING
};
这个变量中,包含了二个变量 ngx_http_m3u9_module_ctx, ngx_http_m3u9_commands,
模块上下文变量,模块命令变量, 更深刻的理解你需要跟踪阅读头文件定义。这里忽略。
user_init_onprocess 是进程创建时用户初始化入口地址
你可以在这里放自己的初始化代码,例如分配内存,打开文件等。
static ngx_int_t init_module_handler(ngx_cycle_t* cycle);
我曾经不知道此处代码的作用,而将初始化代码也放在执行代码 ngx_http_m3u9_handler 中。
当然,为了保证只执行一次,需要用一个变量has_init 标识。
而现在有了这个接口,可见nginx 结构设计还是很切且。
user_uninit_onprocess 是反初始化入口地址。看一下代码更明白
static ngx_command_t ngx_http_m3u9_commands[] =
{
{
ngx_string("m3u9"),
NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
ngx_http_m3u9,
0,
0,
NULL
},
ngx_null_command
};
ngx_http_m3u9 是回调函数地址
该函数会被调用一次,用以得到重要的,以后url 访问时都会调用的函数地址。
static char* ngx_http_m3u9(ngx_conf_t* cf, ngx_command_t* cmd, void* conf);
这里定义为 ngx_http_m3u9_handler
pclcf->handler = ngx_http_m3u9_handler;
http 请求的处理就是在ngx_http_m3u9_handler 中进行的。
演示代码是打开设备,读取数据并返回
这里演示一下404 Not Found 页面是如何被传回的。
1. 用字符串定义404 Not Found 页面, 这个是要求被传回的数据
u_char not_find_404[] = "<html><head>\
<meta http-equiv=\"content-type\" content=\"text/html; charset=windows-1252\"><title>404 Not Found</title></head>;\
<body bgcolor=\"white\">\
<center><h1>404 Not Found</h1></center>\
<hr><center>nginx/1.4.0</center>\
\
\
</body></html>";
2. http 传输时需要发送头部信息。ngx 用下列语句
ngx_http_send_header(request);
发送之前,需要准备好header 数据。
request->headers_out.content_type.data = (u_char*)"text/html";
request->headers_out.content_type.len = sizeof("text/html") - 1;
request->headers_out.content_length_n = sizeof(not_find_404)-1;
request->headers_out.status = NGX_HTTP_NOT_FOUND;
可见,它说明了数据类型是text/html, 并包含了数据的长度和页面状态
3. 定义一个ngx_chain_t 变量, 填充好后,调用过滤器,反馈给用户
ngx_chain_t out;
out.next = NULL;
out.buf = b;
ngx_int_t retFilter = ngx_http_output_filter(request, &out);
return retFilter;
4.数据如何付给out.buf, b 是一个ngx_buf_t 结构地址。所以需要填充这个结构
ngx_buf_t* b = ngx_pcalloc(request->pool, sizeof(ngx_buf_t));
// 填充属性等
b->memory = 1;
b->last_buf = 1;
// 填充数据起始地址, 终了地址
b->pos = not_find_404;
b->last = not_find_404 + strlen((char *)not_find_404) ;
经过以上步骤,才把内存中数据传输到客户端。
可见, 它比简单的字符串信息复杂很多。为什么呢?
这是架构所决定的。
1. 要求先发header, 说明类型,内容长度等, 这样可以支持二进制或者其它数据类型
2. 填充结构,说明其它额外信息,不仅仅只是数据地址,长度。
这样它可以应付复杂的数据类型,包括存储类型等。或者还没有考虑到的地方。
static ngx_http_module_t ngx_http_m3u9_module_ctx =
{
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
其它变量都为常数或空,用户可自行查找浏览。
丙. 编写配置文件
1. config
这个 config 是模块目录下文件, 供./configure 使用,内容如下
ngx_addon_name=ngx_http_m3u9_module
HTTP_MODULES="$HTTP_MODULES ngx_http_m3u9_module"
CORE_LIBS="$CORE_LIBS -lrt"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
$ngx_addon_dir/src/ngx_http_m3u9_module.c"
关于模块的名字,和代码中模块名字要一致。
补充一下,文件的目录结构为。
nginx/mymodules // dir
nginx/mymodules/config // file, need edit
nginx/mymodules/src // dir
nginx/mymodules/src/ngx_http_m3u9_module.c // file, need edit
2. nginx.conf
Nginx配置文件中的指令一般分为main,server,location,upstream四种;
main: 全局指令,比如本文中 worker_processes 1这些;
server:特定主机相关的配置;
location:特定uri相关的配置;
upstream:上游服务器相关配置,fastcgi,proxy_pass这类都可能用到;
这里我们只有找到 location / 节处,与它并排放一个新的节
location /m3u9
{
m3u9
}
/m3u9是url的访问地址,{}中m3u9 和 commands 定义的字符串要一致。
丁. 运行configure, 生成Makefile
./configure --add-module=./mymodules/m3u9/
后面参数指明模块所在位置。
看到下面输出表示无误了。
dding module in ./mymodules/m3u9/
+ ngx_http_m3u9_module was configured
当时我还手工修改makefile, how silly am I, nginx 都为我们处理好了。
nginx 可以直接支持c 文件, 如果你用了c++代码, 需要手工修改 Makefile
只需将对CPP 的编译改为 g++, 链接也用 g++ 就可以了。
./configure --help 可以看到帮助,
可以用--prefix 设定路径, --add-module 添加模块
戊。编译和安装
make, 当然要通过。
make install , 看清它装哪里了。跟configure 显示的路径是一致的。
己。测试
http://localhost/m3u9/abc.ts 就可以调用到我们的代码了,用两台电脑更好。
我在代码中加了log, 在error 文件中有log记录。
一切都从简了,abc.ts 是虚指,反正我的演示代码不管请求什么文件,都随意返回2M数据
据说在nginx.conf 中把master_process off, daemon off, 可以方便gdb 调试。
由于我的代码很简单,自己的代码在用户层先调好(可以用gdb)
此处就不劳gdb 大架了。有log 就可以应付了。