nginx模块开发并不是那么容易, 从行数上来讲, 淘宝给出的tengine给出的那个所谓hello模块的长度也到了245行, 要想真正独立写出这么多代码, 对于我来说是非常难的.
245行, 如果是nodejs, 已经可以写一个比较完善的文件服务器了. 要想完全理解这个hello模块, 有c基础的也怕是要花不少时间, 像我这样没有c经验的, 更是难上加难.
我决定写一个真正的hello模块,也就是最最简单的那种,能自己写出来,也算是至少nginx模块开发入门了.
这个hello模块作用就是当访问/test
的时候, 返回一段固定的html代码(一个字符串).
此模块一共不到60行, 理解此模块比理解淘宝教程的模块要简单几倍.
先上全代码
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static char *set(ngx_conf_t *, ngx_command_t *, void *);
static ngx_int_t handler(ngx_http_request_t *);
static ngx_command_t test_commands[] = {
{
ngx_string("test"),
NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
set,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
static ngx_http_module_t test_ctx = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};
ngx_module_t ngx_http_test_module = {
NGX_MODULE_V1,
&test_ctx,
test_commands,
NGX_HTTP_MODULE,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NGX_MODULE_V1_PADDING
};
static char *set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
ngx_http_core_loc_conf_t *corecf;
corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
corecf->handler = handler;
return NGX_CONF_OK;
};
static ngx_int_t handler(ngx_http_request_t *req) {
u_char html[1024] = "<h1>This is Test Page!</h1>";
req->headers_out.status = 200;
int len = sizeof(html) - 1;
req->headers_out.content_length_n = len;
ngx_str_set(&req->headers_out.content_type, "text/html");
ngx_http_send_header(req);
ngx_buf_t *b;
b = ngx_pcalloc(req->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(req, &out);
}
这个文件的路径是src/http/module/ngx_http_test_module.c
.
光是放这个是nginx的makefile是不知道的,它不会去编译新增的模块, 还需要在auto/modules
这个文件中加入
if [ $HTTP_ACCESS = YES ]; then
HTTP_MODULES="$HTTP_MODULES $HTTP_ACCESS_MODULE"
HTTP_SRCS="$HTTP_SRCS $HTTP_ACCESS_SRCS"
fi
# 上面是原有的, 这里才是加上的
HTTP_MODULES="$HTTP_MODULES ngx_http_test_module"
HTTP_SRCS="$HTTP_SRCS src/http/modules/ngx_http_test_module.c"
auto是用来生成Makefile的很多shell脚本,Nginx没有用那些构建工具来制作自己的Makefile, 而是自己写了大量的shell脚本, 学习这些脚本对于自己的shell编程也是很有帮助的. nginx的编译的生成文件都在objs
中, 清晰明了, 因此make clean
也只是调用rm -rf objs
即可, 非常简洁.
总之加上上面两句话, nginx就知道你要新增这个模块了, 顺序应该不是很要紧(其实我是没试过).
这样我们的模块依然不起作用, 还需要修改配置文件, nginx启动完全依靠那个conf/nginx.conf
的配置文件!
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
location /test {
test;
}
我们在http中的server中加上location /test
来插入我们的模块.
运行之, 在浏览器中访问你的域名/test
就能看到This is Test Page
几个大字, 因为是<h1>
的嘛!
解释下这段代码吧.
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_config.h>
包含三个关键头文件, 这没什么异议, 注意, 我们写的模块基本都是http的.
static char *set(ngx_conf_t *, ngx_command_t *, void *);
static ngx_int_t handler(ngx_http_request_t *);
声明两个函数, 这是两个非常重要的函数, 后面主要讲.
command注册
static ngx_command_t test_commands[] = {
{
ngx_string("test"),
NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
set,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
这是定义一个配置命令信息数组(为何是数组暂时还真不知道), 数组最后一个元素都是ngxnullcommand.
结构体第一个参数尤为重要, 这里是test, 指的是我们在配置文件中输入test(不是路径的/test
).这样指定后, nginx在读取配置的时候读到test命令, 才会把接管权给我们.也就是把请求转给我们去处理.
第二个参数代表我们的模块注册的是http location的命令, 并且接受0个参数.http location当然就是指这个命令触发是跟路径有关的.
事实上大量的模块触发都跟路径相关, 比如php, php就是接管所有后缀是
.php
的location.php的nginx模块配置如下所示
location ~ \.php$ {
root /home/www;
fastcgi_pass 127.0.0.1:3344;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
include fastcgi_params;
}
第三个参数是set, 这是一个函数指针,也就是我们一开始声明的两个关键函数中的一个,读到我们注册的test
这个命令的时候触发的, 我们一般在set中写上托管http请求的handler函数.
第四个参数是offset类型, 用来结构体的偏移的, 不再hello模块的讨论范围, loc conf类型就直接是NGX_HTTP_LOC_CONF_OFFSET
就行.
第五个参数就是具体offset的值, 我们这里只有一个命令, 没有参数, 输入0即可.
第六个参数NULL即可, 作用未知.
回调函数
写完命令注册, 我们还需要一个context,至于为何叫上下文我也不知道, 但这种把函数作为参数的方法在nodejs中一般叫回调函数,内核程序员喜欢叫它hook, 钩子函数, 我觉得也很形象.但是在hello模块中,我们可以完全不用这些回调机会.
static ngx_http_module_t test_ctx = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};
nginx一共提供8个回调机会, 具体是什么时候用不在hello模块讨论范围, 这里都设置为NULL即可.
模块结构体
最后写上真正暴露给外面的模块结构体
ngx_module_t ngx_http_test_module = {
NGX_MODULE_V1,
&test_ctx,
test_commands,
NGX_HTTP_MODULE,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NGX_MODULE_V1_PADDING
};
其中NGX_MODULE_V1
和NGX_MODULE_V1_PADDING
都是宏定义, 不必去管
只需知道第二个放上回调函数数组, 第三个是注册命令, 第四个是模块类型即可.后面7个NULL.
set函数
前面说到, set函数就是给配置信息挂上托管http请求的handler函数的.
static char *set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
ngx_http_core_loc_conf_t *corecf;
corecf = ngx_http_conf_get_module_loc_conf(cf->pool, ngx_http_core_module);
corecf->handler = handler;
return NGX_CONF_OK;
}
返回类型是char *
,它有两种返回值, 一个是NGX_CONF_OK
也就是NULL, 另一个是NGX_CONF_ERROR
也就是(void *) -1
.
里面最重要的就是corecf->handler = handler
了, 这句话把handler函数挂到corecf的handler属性上.
handler函数
这是最重要的函数, nodejs中写http服务器, 完全只要写个handler就行了.nginx却要写前面一堆废话.
static ngx_int_t handler(ngx_http_request_t *req) {
char html[1024] = "<h1>This is Test Page</h1>";
int len = sizeof(html) - 1;
req->headers_out.status = 200;
req->headers_out.content_length_n = len;
ngx_str_set(&req->headers_out.content_type, "text/html");
ngx_http_send_header(req);
ngx_buf_t *b;
b = ngx_pcalloc(req->pool, sizeof(ngx_buf_t));
b->pos = html;
b->last = html + len;
b->memory = 1;
b->last_buf = 1;
ngx_chain_t out;
out.buf = b;
out.next = NULL;
return ngx_http_output_filter(req, &out);
};
其实主菜才是最直观的, 前面是设置http的返回头部, 后面是设置http body.简单至极.每每写到handler部分都神清气爽, 感觉自己也会用c了..
补充一下. 我在用jekyll写博客的时候又出现的编译错误, 原因是使用了高亮shell
, 不知道是不是因为没有shell, 反正它居然报错了, 修改成bash后显示正确, 这实在让我费解, 我仅仅是写一个博客而已, 你居然来个编译错误.不支持你就不管呗. jekyll用的液体模板并不能报出模板哪里错了, 总之出错了,不得不用排除法去猜, 让人郁闷.
牛逼的是我发现github貌似也使用的redcarpet
,见github bloghttps://github.com/blog/832-rolling-out-the-redcarpet
github的GitHub Flavored Markdown,支持的高亮语言有这些:https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
非常可惜的是我们没法用这么全的.