nginx模块开发入门

1 前言

这是本人一边看着教程一边敲c代码记的笔记,写得比较随意。欢迎来信指出错误;(邮箱:xurenlu @ gmail.com ),blog:http://www.162cm.com/; 本文在网上随时更新:http://www.162cm.com/p/ngx_ext.html

2 开发nginx模块之Hello World篇(手把手走一遍)

2.1 进行echo模块的功能设计

以下是本模块要能识别的nginx配置

 

作为演示模块,我们这个模块仅仅完成以下功能:

  1. 读入nginx.conf中以echo开头的配置;echo是本模块新加入的命令,意思是直接输出; 例如:
  2. 在用户访问/hello时设置文件头为content-type=application/html;
  3. 在用户访问/hello时输出指定的欢迎词,比如“Hi,this is a demo module”;

2.2 准备nginx的源代码

可直接到[http://www.nginx.net/]下载,解压缩;

~#tar -xzf nginx-0.8.9.tar.gz

2.3 准备好nginx的配置文件,越简单越好,并且要打开调试,关闭daemon模式;

~#vim nginx.conf
worker_processes  1;
daemon off; 
master_process  off;
error_log  /tmp/error.log debug;
pid /tmp/nginx_demo.pid;
events {
    worker_connections  1024;
}
http {
    include       /etc/nginx/mime.types;
    sendfile        on;
    keepalive_timeout  65;
    tcp_nodelay        on;
    server {
        listen   8100;
        server_name  localhost;
        access_log  /tmp/access.log;
        error_log  /tmp/error.log debug;
        location /hello {
            echo "Hi,this is a demo module";
        }
    }
}

2.4 创建nginx模块目录

~#mkdir ngx_module_echo

2.5 编辑nginx模块的编译相关文件(config)

!#vim ngx_module_echo/config

其内容为:

ngx_addon_name=ngx_module_echo
HTTP_MODULES="$HTTP_MODULES ngx_module_echo"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_module_echo.c"
CORE_LIBS="$CORE_LIBS "
#CORE_LIBS="$CORE_LIBS -lm"

其实这里的意思也很好理解,这里告诉nginx编译的时候应该在哪里找到模块的源文件;这里指出了我们要包含进来的源文件是ngx_module_echo.c,编辑它,具体内容后面会给出:

#vim ngx_module_echo/ngx_module_echo.c

,然后试着配置一下nginx:

~#cd nginx-0.8.9
~/nginx-0.8.9/#./configure --add-module=~/ngx_module_echo/

,会报错:

./configure: error: no ~/ngx_module_echo//config was found

那是因为我们没有编辑好~/ngx_module_echo/ngx_module_echo.c这个文件,把它的内容改为:

 

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

static char* ngx_echo_readconf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void* ngx_echo_create_loc_conf(ngx_conf_t *cf);
static char* ngx_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);

typedef struct {
    ngx_str_t ecdata;
    ngx_flag_t           enable;
} ngx_echo_loc_conf_t;

static ngx_command_t  ngx_echo_commands[] = {
    { ngx_string("echo"),
      NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_echo_readconf,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_echo_loc_conf_t, ecdata),
      NULL },
      ngx_null_command
};


static ngx_http_module_t  ngx_echo_module_ctx = {
    NULL,                          /* preconfiguration */
    NULL,           /* postconfiguration */

    NULL,                          /* create main configuration */
    NULL,                          /* init main configuration */

    NULL,                          /* create server configuration */
    NULL,                          /* merge server configuration */

    ngx_echo_create_loc_conf,  /* create location configuration */
    ngx_echo_merge_loc_conf /* merge location configuration */
};


ngx_module_t  ngx_module_echo = {
    NGX_MODULE_V1,
    &ngx_echo_module_ctx, /* module context */
    ngx_echo_commands,   /* module directives */
    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
};


static ngx_int_t
ngx_echo_handler(ngx_http_request_t *r)
{
    printf("called:ngx_echo_handler\n");
    ngx_int_t     rc;
    ngx_buf_t    *b;
    ngx_chain_t   out;

    ngx_echo_loc_conf_t  *cglcf;
    cglcf = ngx_http_get_module_loc_conf(r, ngx_module_echo);
    // 必须是 GET 或者 HEAD 方法,否则返回 405 Not Allowed
    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
        return NGX_HTTP_NOT_ALLOWED;
    }
    if (r->headers_in.if_modified_since) {
        return NGX_HTTP_NOT_MODIFIED;
    }
   /* 设置返回的 Content-Type。注意,ngx_str_t 有一个很方便的初始化宏 ngx_string,它可以把ngx_str_t 的 data 和 len 成员都设置好 */
    
	// 设置 Content-Type
	r->headers_out.content_type.len = sizeof("text/html") - 1;
    r->headers_out.content_type.data = (u_char *) "text/html";
 
    // 等同于 ngx_str_t type = ngx_string("text/plain");
    
	// 设置返回状态码
    r->headers_out.status = NGX_HTTP_OK;
	// 响应包是有包体内容的,需要设置 Content-Length 长度
    r->headers_out.content_length_n = cglcf->ecdata.len;
    // 发送 HTTP 头部
    if (r->method == NGX_HTTP_HEAD) {
        rc = ngx_http_send_header(r);

        if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
            return rc;
        }
    }
    //构造 ngx_buf_t 结构体准备发送包体
    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;
    }
    // 赋值 ngx_buf_t
    out.buf = b;
	// 设置 next 为 NULL
    out.next = NULL;
    

    b->pos = cglcf->ecdata.data;
   // 注意,一定要设置好 last 指针
    b->last = cglcf->ecdata.data+(cglcf->ecdata.len);

    b->memory = 1;
	// 声明这是最后一块缓冲区
    b->last_buf = 1;
	 
    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }
	/* 最后一步为发送包体,发送结束后 HTTP 框架会调用 ngx_http_finalize_request 方法结束请求 */
    return ngx_http_output_filter(r, &out);
}
static char *
ngx_echo_readconf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    printf("called:ngx_echo_readconf\n");
    ngx_http_core_loc_conf_t  *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_echo_handler;
    ngx_conf_set_str_slot(cf,cmd,conf);
    return NGX_CONF_OK;
}


static void *
ngx_echo_create_loc_conf(ngx_conf_t *cf)
{
    printf("called:ngx_echo_create_loc_conf\n");
    ngx_echo_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_echo_loc_conf_t));
    if (conf == NULL) {
        return NGX_CONF_ERROR;
    }

    conf->ecdata.len=0;
    conf->ecdata.data=NULL;
    conf->enable = NGX_CONF_UNSET;
    return conf;
}
static char *
ngx_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    printf("called:ngx_echo_merge_loc_conf\n");
    ngx_echo_loc_conf_t *prev = parent;
    ngx_echo_loc_conf_t *conf = child;

    ngx_conf_merge_str_value(conf->ecdata, prev->ecdata, 10);
    ngx_conf_merge_value(conf->enable, prev->enable, 0);
/**
    if(conf->enable)
        ngx_echo_init(conf);
        */
    return NGX_CONF_OK;
    return NGX_CONF_OK;
}

以上为原版,以下为《深入解析Nginx》书中的内容:

 

 

static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
// 必须是 GET 或者 HEAD 方法,否则返回 405 Not Allowed
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
return NGX_HTTP_NOT_ALLOWED;
}
// 丢弃请求中的包体
ngx_int_t rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
/* 设置返回的 Content-Type。注意,ngx_str_t 有一个很方便的初始化宏 ngx_string,它可以把
ngx_str_t 的 data 和 len 成员都设置好 */
ngx_str_t type = ngx_string("text/plain");
// 返回的包体内容
ngx_str_t response = ngx_string("Hello World!");
// 设置返回状态码
r->headers_out.status = NGX_HTTP_OK;
// 响应包是有包体内容的,需要设置 Content-Length 长度
r->headers_out.content_length_n = response.len;
// 设置 Content-Type
r->headers_out.content_type = type;
// 发送 HTTP 头部
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {return rc;
}
// 构造 ngx_buf_t 结构体准备发送包体
ngx_buf_t *b;
b = ngx_create_temp_buf(r->pool, response.len);
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// 将 Hello World 复制到 ngx_buf_t 指向的内存中
ngx_memcpy(b->pos, response.data, response.len);
// 注意,一定要设置好 last 指针
b->last = b->pos + response.len;
// 声明这是最后一块缓冲区
b->last_buf = 1;
// 构造发送时的 ngx_chain_t 结构体
ngx_chain_t out;
// 赋值 ngx_buf_t
out.buf = b;
// 设置 next 为 NULL
out.next = NULL;
/* 最后一步为发送包体,发送结束后 HTTP 框架会调用 ngx_http_finalize_request 方法结束请求 */
return ngx_http_output_filter(r, &out);
}



 

下面好了,再回到nginx的源目录configure:
~/nginx-0.8.9/#./configure --add-module=/home/renlu/ngx_module_echo/ --with-
                                        debug
.... 这里省却输出;
make

然后运行一下看看,先测一下配置文件的正确性:

~/nginx-0.8.9/#./objs/nginx -c /home/renlu/ngx_module_echo/nginx.conf -t
called:ngx_echo_create_loc_conf
called:ngx_echo_create_loc_conf
called:ngx_echo_create_loc_conf
called:ngx_echo_readconf
called:ngx_echo_merge_loc_conf
called:ngx_echo_merge_loc_conf
the configuration file /home/renlu/ngx_module_echo/nginx.conf syntax is ok
configuration file /home/renlu/ngx_module_echo/nginx.conf test is successful

运行之:

~/nginx-0.8.9/#./objs/nginx -c /home/renlu/ngx_module_echo/nginx.conf
called:ngx_echo_create_loc_conf
called:ngx_echo_create_loc_conf
called:ngx_echo_create_loc_conf
called:ngx_echo_readconf
called:ngx_echo_merge_loc_conf
called:ngx_echo_merge_loc_conf

在另一个终端执行一个curl:

~#curl http://localhost:8100/hello
Hi,this is a demo module

好了,大功告成,nginx模块版的hello world就到这里了;

3 Nginx模块开发之入门介绍

3.1 Nginx 配置文件

Nginx配置文件中的指令一般分为main,server,location,upstream四种;

  1. main: 全局指令,比如本文中 worker_processes 1这些;
  2. server:特定主机相关的配置;
  3. location:特定uri相关的配置;
  4. upstream:上游服务器相关配置,fastcgi,proxy_pass这类都可能用到;

3.2 Nginx 模块的分类

Nginx的模块一般也分为三种:

  1. handlers :直接处理请求,进行输出内容,修改headers信息等操作;handlers处理器模块一般只能有一个;
  2. filters (过滤器模块),对其他处理器模块输出的内容进行修改操作,最后再交给nginx去输出;
  3. proxies (代理类模块),其实也就是upstream类的模块;主要跟后端一些服务比如fastcgi等操交互,代理,负载均衡都属于这类;
    其实这些nginx模块具体的处理函数原型都是一样的:
    static ngx_int_t ngx_http_module_handler (ngx_http_request_t *r);

只是内部的处理各不相同,而且分为三种,也只是对它们具体做的事情的特点做了一个总结而已; 处理器模块一般是自己进行处理,过滤器模块是在原来的输出上进行修改,而上游模块是再去连接其他网络地址,请求一定内容来输出.

3.3 Nginx模块的加载

  1. Nginx 模块是被编译进入了nginx,而不像apache是编译一个so文件在配置文件中指定是否加载
  2. 解析配置文件时,Nginx 的各个模块都有机会去接手处理某个请求;但是,同一URI处理请求的模块只能有一个,所有的模块会“竟争”这个,不会出到地址”/index.php”这一个location同时被fastcgi和proxy两个模块处理的情况;至于竟争的过程….我还没弄清楚,需要继续读代码才知道;
  3. nginx模块可以在任何时候发生效果,比如:
    1. 在读取配置文件之前
    2. 读到存在 location 和 server 下或其他任何部分下的每一个配置指令
    3. 当 Nginx 初始化全局部分配置时
    4. 当 Nginx 初始化server部分配置时
    5. 当 Nginx 将全局部分的配置与server部分的配置合并的时候
    6. 当 Nginx 初始化location部分配置的时候
    7. 当 Nginx 将其上层server配置与位置(location)部分配置合并的时候
    8. 当 Nginx 的主进程开始的时候
    9. 当一个新的工作进程(worker)开始的时候
    10. 当一个工作进程退出的时候
    11. 当主进程退出的时候
    12. 在处理请求时
    13. 在过滤回应(response)的头部
    14. 在过滤回应(response)的主体
    15. 选择一台后端服务器时
    16. 初始化到后端服务器的请求
    17. 重新初始化到后端的服务器的请求(reinit_request)
    18. 处理来自后端服务器的回复
    19. 完成与后端服务器的交互

转自:http://www.162cm.com/p/ngx_ext.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值