Nginx是当前最流行的HTTP Server之一,根据W3Techs的统计,目前世界排名(根据Alexa)前100万的网站中,Nginx的占有率为6.8%。与Apache相比,Nginx在高并发情况下具有巨大的性能优势。
同时,大量的第三方扩展模块也令Nginx越来越强大。
最牛的还是由淘宝的工程师清无(王晓哲)和春来(章亦春)所开发的nginx_lua_module可以将Lua语言嵌入到Nginx配置中,从而利用Lua极大增强了Nginx本身的编程能力,甚至可以不用配合其它脚本语言(如PHP或Python等),只靠Nginx本身就可以实现复杂业务的处理。
还有一个春来所开发的ngx_openresty更是通过集成LuaJIT等组件,将Nginx本身变成了一个完全的应用开发平台。
这个OpenResty 集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。 ☆( ̄▽ ̄)/$:*相当的给力的(这个才是我要学习使用的,标准的Nginx模块开发用的是C++,太难了。。(ノ`Д)ノ)
下载Nginx源码包,地址:http://nginx.org/download/ 。然后解压安装。【运行环境:CentOS 7.2】
wget http://nginx.org/download/nginx-1.9.9.tar.gz # 用最新的试试
tar -xzvf nginx-1.9.9.tar.gz
./configure --prefix=/usr/local/nginx
make
make install
# 如有报错,请对应安装依赖!
yum install pcre-devel # nginx的依赖包pcre,没安装会报错
yum install -y zlib-devel # 使用./configure+参数 需要此依赖包
yum -y install openssl openssl-devel # error: the HTTP cache module requires md5 functions
运行,检查与停止【Nginx默认以Deamon(守护进程)进程启动 】
/usr/local/nginx/sbin/nginx # 开启Nginx
curl -i http://localhost/ # 检测Nginx是否已经成功
/usr/local/nginx/sbin/nginx -s stop # 停止Nginx
配置文件可以看做是Nginx的灵魂,Nginx服务在启动时会读入配置文件,而后续几乎一切动作行为都是按照配置文件中的指令进行的,因此如果将Nginx本身看做一个计算机,那么Nginx的配置文件可以看成是全部的程序指令。
以下为Nginx默认配置文件:
通常它会在nginx安装目录的conf下,nginx安装在/usr/local/nginx,主配置文件默认放在/usr/local/nginx/conf/nginx.conf。
#user nobody;
worker_processes 1; # 工作线程数
error_log logs/error.log;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
# 配置文件包含,此处包含了mine.types这个配置文件,此文件指定了各种HTTP Content-type
include mime.types;
default_type application/octet-stream;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
location / {
root /home/yefeng/www;
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 html;
}
}
}
配置文件的规则和内容也是相当多的,另外整理一份文档进行记录。此处就略过了。。。
这个模块开发是使用c/c++的,非常不适合我这种菜鸟学习,所以这里就看看了解一下就好了。
分析的东东,就简单的记录一下,详细的内容可查看Nginx官方开发文档,或者书籍例如:《Nginx模块开发指南》
(Nginx本身支持多种模块,如HTTP模块、EVENT模块和MAIL模块,本文只讨论HTTP模块)
Nginx本身做的工作实际很少,当它接到一个HTTP请求时,它仅仅是通过查找配置文件将此次请求映射到一个location block,而此location中所配置的各个指令则会启动不同的模块去完成工作,因此模块可以看做Nginx真正的劳动工作者。 通常一个location中的指令会涉及一个handler模块和多个filter模块(当然,多个location可以复用同一个模块)。**handler模块负责处理请求,完成响应内容的生成,而filter模块对响应内容进行处理。**因此Nginx模块开发分为handler开发和filter开发(不考虑load-balancer模块)。
一次常规请求和响应的过程差不多想这样:
Http Request ==>> [Conf Nginx Core] ==>> choose a handler based conf ==>> [Handler] ==>> [Filter 1] ==>> [Filter 2] ==>> [Filter N] ==>> Http Response
这不是我写的,我只能勉强看懂,只能直接贴源码,把一些说明直接备注在源码边上了╮(╯▽╰)╭
开发一个叫echo的handler模块,这个模块功能非常简单,它接收“echo”指令,指令可指定一个字符串参数,模块会输出这个字符串作为HTTP响应。
例如,做如下配置:
location /echo { echo "hello nginx";}
则访问http://hostname/echo时会输出hello nginx。
直观来看,要实现这个功能需要三步:1、读入配置文件中echo指令及其参数;2、进行HTTP包装(添加HTTP头等工作);3、将结果返回给客户端。下面本文将分部介绍整个模块的开发过程。
然后,直接跳过介绍,源码如下:
/*
* Copyright (C) Eric Zhang
*/
#include
#include
#include
/* Module config 模块配置信息结构 结构的命名规则为ngx_http_[module-name]_[main|srv|loc]_conf_t*/
typedef struct {
ngx_str_t ed; /* ed用于存储echo指令指定的需要输出的字符串 */
} ngx_http_echo_loc_conf_t;
static char *ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
/* Directives 定义指令 其中每一个元素表示一个条指令 ngx_command_s定义在core/ngx_config_file.h中*/
static ngx_command_t ngx_http_echo_commands[] = {
{ /* name 模块名称 */
ngx_string("echo"),
/* type NGX_CON F_TAKE1-7表示精确接收1-7个 */
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
/* set 函数指针,用于指定一个参数转化函数 */
ngx_http_echo,
/* conf 指定Nginx相应配置文件内存其实地址,一般可以通过内置常量指定,如NGX_HTTP_LOC_CONF_OFFSET*/
NGX_HTTP_LOC_CONF_OFFSET,
/* C 库宏 offsetof(type, member-designator) 会生成一个类型为 size_t 的整型常量,它是一个结构成员相对于结构开头的字节偏移量。成员是由 member-designator 给定的,结构的名称是在 type 中给定的。 */
offsetof(ngx_http_echo_loc_conf_t, ed),
NULL },
ngx_null_command
};
/* Http context of the module 定义一个ngx_http_module_t类型的结构体变量
一共有8个Hook注入点,分别会在不同时刻被Nginx调用 不需要的注入点设为NULL */
static ngx_http_module_t ngx_http_echo_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
/* create location configration 初始化一个配置结构体,如为配置结构体分配内存等工作*/
ngx_http_echo_create_loc_conf,
/* merge location configration 将其父block的配置信息合并到此结构体中,也就是实现配置的继承*/
ngx_http_echo_merge_loc_conf
/*注意这里的命名规则:ngx_http_[module-name]_[create|merge]_[main|srv|loc]_conf。*/
};
/* Module 组合Nginx Module
* 一个Nginx模块被定义为一个ngx_module_t结构,这个结构的字段很多,不过开头和结尾若干字段一般可以通过Nginx内置的宏去填充*/
ngx_module_t ngx_http_echo_module = {
NGX_MODULE_V1, /*宏填充了若干字段,就不去深究了*/
&ngx_http_echo_module_ctx, /* module context 注入点 结构体变量*/
ngx_http_echo_commands, /* module directives 定义指令 */
/*HTTP模块 其它可用类型还有NGX_EVENT_MODULE(事件处理模块)和NGX_MAIL_MODULE(邮件模块)*/
NGX_HTTP_MODULE, /* module type */
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 /*宏填充了若干字段,就不去深究了*/
};
/* Handler function
需要完成的内容 * 读入模块配置。* 处理功能业务。* 产生HTTP header。* 产生HTTP body。*/
/* ngx_http_request_t的headers_in、headers_out和chain,它们分别表示request header、response header和输出数据缓冲区链表 */
static ngx_int_t
ngx_http_echo_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t out;
ngx_http_echo_loc_conf_t *elcf;
/* 获取模块配置信息 */
elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST)))
{
return NGX_HTTP_NOT_ALLOWED;
}
/* 设置response header
ngx_http_headers_out_t定义了所有可以设置的HTTP Response Header信息*/
r->headers_out.content_type.len = sizeof("text/html") - 1;
r->headers_out.content_type.data = (u_char *) "text/html";
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = elcf->ed.len;
/*设置好头信息后使用ngx_http_send_header就可以将头信息输出,ngx_http_send_header接受一个ngx_http_request_t类型的参数*/
if(r->method == NGX_HTTP_HEAD)
{
rc = ngx_http_send_header(r);
if(rc != NGX_OK)
{
return rc;
}
}
/* 输出Response body */
/* Nginx的I/O机制,Nginx允许handler一次产生一组输出,可以产生多次,Nginx将输出组织成一个单链表结构,链表中的每个节点是一个chain_t,定义在core/ngx_buf.h:*/
/* ngx_but_t中比较重要的是pos和last,分别表示要缓冲区数据在内存中的起始地址和结尾地址 */
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;
}
out.buf = b;
out.next = NULL;
b->pos = elcf->ed.data;
b->last = elcf->ed.data + (elcf->ed.len);
b->memory = 1;
/*last_buf是一个位域,设为1表示此缓冲区是链表中最后一个元素,为0表示后面还有元素*/
b->last_buf = 1;
rc = ngx_http_send_header(r);
if(rc != NGX_OK)
{
return rc;
}
/* 用ngx_http_output_filter就可以输出了(会送到filter进行各种过滤处理)*/
/* ngx_http_output_filter会遍历链表,输出所有数据 */
return ngx_http_output_filter(r, &out);
}
/* 转换函数 */
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);
/* 修改核心配置模块hander替换为 自己编写的ngx_http_echo_handler */
clcf->handler = ngx_http_echo_handler;
/* ngx_conf_set_str_slot将裸字符串转化为ngx_str_t */
ngx_conf_set_str_slot(cf,cmd,conf);
return NGX_CONF_OK;
}
/* 初始化一个配置结构体,如为配置结构体分配内存等工作 */
static void *
ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_echo_loc_conf_t *conf;
/* ngx_pcalloc用于在Nginx内存池中分配一块空间 是pcalloc的一个包装 */
/* 使用ngx_pcalloc分配的内存空间不必手工free,Nginx会自行管理,在适当是否释放。*/
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
if (conf == NULL) {
return NGX_CONF_ERROR;
}
conf->ed.len = 0;
conf->ed.data = NULL;
return conf;
}
/* 将其父block的配置信息合并到此结构体中,也就是实现配置的继承。*/
/* 将父block域的配置信息合并到create_loc_conf新建的配置结构体中 */
static char *
ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_echo_loc_conf_t *prev = parent;
ngx_http_echo_loc_conf_t *conf = child;
/* ngx_conf_merge_str_value不是一个函数,而是一个宏,其定义在core/ngx_conf_file.h中 */
ngx_conf_merge_str_value(conf->ed, prev->ed, "");
return NGX_CONF_OK;
}
Nginx不支持动态链接模块,所以安装模块需要将模块代码与Nginx源代码进行重新编译。 (这个相当的麻烦)
1、编写模块config文件,这个文件需要放在和模块源代码文件放在同一目录下。文件内容如下:
ngx_addon_name=模块完整名称
HTTP_MODULES="$HTTP_MODULES 模块完整名称"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/源代码文件名"
2、进入Nginx源代码,使用下面命令编译安装
./configure --prefix=安装目录 --add-module=模块源代码文件目录
make
make install
这样就完成安装了,例如,我的源代码文件放在/home/xiaohang/ngxdev/ngx_http_echo下,我的config文件为:
ngx_addon_name=ngx_http_echo_module
HTTP_MODULES="$HTTP_MODULES ngx_http_echo_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_echo_module.c"
编译安装命令为:
./configure --prefix=/usr/local/nginx --add-module=/home/xiaohang/ngxdev/ngx_http_echo
make
make install
这样echo模块就被安装在我的Nginx上了,下面测试一下,修改配置文件,增加以下一项配置:
location /echo { echo "This is my first nginx module!!!";}
然后用curl测试一下:
curl -i http://localhost/echo
得到的结果:(亲测有效)
[root@251picserver sbin]# curl -i http://localhost/echo
HTTP/1.1 200 OK
Server: nginx/1.9.9
Date: Tue, 14 Aug 2018 08:47:10 GMT
Content-Type: text/html
Content-Length: 32
Connection: keep-alive
到这里,Nginx模块简单的开发过程就这些了,需要深入学习,可以阅读源码,在Nginx源代码的core/下放有Nginx的核心代码,对理解Nginx的内部工作机制非常有帮助,http/目录下有Nginx HTTP相关的实现,http/module下放有大量内置http模块【看这个的源码简直太高深了,路过路过…((/- -)/】
Nginx这个这个牛叉的东东,学习的东东可多了。。。