Nginx 模块开发简单了解


文章目录

  • Nginx 模块开发简单了解
  • Nginx简介
  • Nginx Linux下的默认安装与运行
  • Nginx配置文件说明
  • Nginx 模块开发Demo - 使用c/c++
  • Nginx 模块工作概述
  • Nginx 模块开发源码
  • Nginx 模块安装
  • 其他更深入学习的东东
  • 参考文档资料


Nginx简介

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 Linux下的默认安装与运行

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默认配置文件:

通常它会在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;
        }
    }
}

配置文件的规则和内容也是相当多的,另外整理一份文档进行记录。此处就略过了。。。

Nginx 模块开发Demo - 使用c/c++

这个模块开发是使用c/c++的,非常不适合我这种菜鸟学习,所以这里就看看了解一下就好了。

分析的东东,就简单的记录一下,详细的内容可查看Nginx官方开发文档,或者书籍例如:《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

Nginx 模块开发源码

这不是我写的,我只能勉强看懂,只能直接贴源码,把一些说明直接备注在源码边上了╮(╯▽╰)╭

开发一个叫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 <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
/* 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不支持动态链接模块,所以安装模块需要将模块代码与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这个这个牛叉的东东,学习的东东可多了。。。

  • Nginx 配置文件详解 【计划整理一下】
  • Nginx 社区有很多的第三方的模块,各种各样的都有,我自己是没法用这种开发模块的,太难。地址:https://www.nginx.com/resources/wiki/modules/index.html
  • Nginx 可嵌入其他语言开发的模块,Java,php,Lua等。
  • 其中,Lua个人认为适合学习,因为 ngx_openresty 已经集成LuaJIT等组件了,完全可以作为应用开发平台。
  • OpenResty 主页 :Computerwizard2.github.io [计划学习了解一下]
  • 最后的学习当然是 Nginx 源码咯。。。

参考文档资料