前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Nginx 第三方模块使用与开发

Nginx 第三方模块使用与开发

作者头像
Se7en258
发布2021-07-01 15:04:43
1.6K0
发布2021-07-01 15:04:43
举报
文章被收录于专栏:Se7en的架构笔记Se7en的架构笔记

Nginx 允许引入第三方模块来扩展 Nginx 的功能。官方网站 NGINX 3rd Party Modules 列出了 Nginx 很多的第三方模块。除此之外,很多很有用的模块也能在 github 等网站上找到。

添加模块

接下来通过添加 njs 模块为例来介绍如何添加第三方模块。njs 是 Nginx + JavaScript 的缩写,简单来说,就是 Nginx 里面可以运行 JavaScript,用 JavaScript 来构建动态的 Web 应用。Nginx NJS 包含两个 Nginx 扩展模块:ngx_http_js_module 和 ngx_stream_js_module。

首先克隆 njs 模块的代码:

代码语言:javascript
复制
git clone https://github.com/nginx/njs.git

查看当前已安装的 Nginx 的编译信息:

代码语言:javascript
复制
[root@nginx-plus1 nginx-1.14.2]# /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.14.2
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) 
built with OpenSSL 1.0.2k-fips  26 Jan 2017
TLS SNI support enabled
configure arguments: --with-debug --with-stream --prefix=/usr/local/nginx --with-http_ssl_module

进入 Nginx 源代码目录,执行 ./configure,在最后通过 --add-module 指令加上 njs 模块。执行完成后会在当前目录生成编译所需的相关文件(例如 Makefile 和 objs 目录等)。

代码语言:javascript
复制
cd /root/nginx-1.14.2

# 复制前面nginx -V看到的参数,在最后--add-module加上njs模块的路径
./configure --with-debug --with-stream --prefix=/usr/local/nginx --with-http_ssl_module --add-module=/root/njs/nginx

执行完上面命令后,objs 目录下的 ngx_modules.c 文件中的 ngx_modules 数组中可以看到新增的 njs 模块:

代码语言:javascript
复制
cat /root/nginx-1.14.2/objs/ngx_modules.c

接下来执行 make 命令开始编译:

代码语言:javascript
复制
make

编译完成后注意不需要执行 make install 命令,只需要将生成的 Nginx 二进制文件覆盖原先的二进制文件即可。

代码语言:javascript
复制
cp /root/nginx-1.14.2/objs/nginx /usr/local/nginx/sbin/nginx -f

现在展示如何使用 njs 模块的功能。在 nginx 的 conf 目录下新建 js 目录,用于存放 javascript 代码。在 js 目录下创建 http.js 文件,内容如下:

代码语言:javascript
复制
function summary(r) {
    var a, s, h;

    s = "JS summary\n\n";
    //打印请求方法,HTTP版本,Host,客户端地址,URI
    s += "Method: " + r.method + "\n";
    s += "HTTP version: " + r.httpVersion + "\n";
    s += "Host: " + r.headersIn.host + "\n";
    s += "Remote Address: " + r.remoteAddress + "\n";
    s += "URI: " + r.uri + "\n";
    
    //打印请求头
    s += "Headers:\n";
    for (h in r.headersIn) {
        s += "  header '" + h + "' is '" + r.headersIn[h] + "'\n";
    }
    //打印参数内容
    s += "Args:\n";
    for (a in r.args) {
        s += "  arg '" + a + "' is '" + r.args[a] + "'\n";
    }

    return s;
}

function baz(r) {
    //设置响应状态码
    r.status = 200;
    //设置响应头
    r.headersOut.foo = 1234;
    r.headersOut['Content-Type'] = "text/plain; charset=utf-8";
    r.headersOut['Content-Length'] = 15;
    r.sendHeader();
    //设置响应内容
    r.send("nginx");
    r.send("java");
    r.send("script");

    r.finish();
}

//export default用于导出常量、函数、文件、模块等
export default {foo, summary, baz, hello};

编写 Nginx 配置文件 /usr/local/nginx/conf/nginx.conf:

代码语言:javascript
复制
events {}
http {
    #导入js文件
    js_import js/http.js;

    #设置变量,变量值为调用js文件的相应函数的返回值
    js_set $foo     http.foo;
    js_set $summary http.summary;

    server {
        listen 8000;

        location / {
            add_header X-Foo $foo; #将变量foo的结果添加到响应头中 
            js_content http.baz; #执行其中 JS 内容并输出
        }

        location = /summary {
            return 200 $summary;  #返回变量summary的结果
        }
    }
}

启动 Nginx:

代码语言:javascript
复制
/usr/local/nginx/sbin/nginx

客户端请求 Nginx:

代码语言:javascript
复制
[root@nginx-plus1 ~]# curl localhost:8000 -i
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Tue, 15 Jun 2021 14:04:39 GMT
Content-Type: text/plain; charset=utf-8
Connection: keep-alive
foo: 1234
Content-Length: 15
X-Foo: foo

nginxjavascript  


[root@nginx-plus1 ~]# curl localhost:8000/summary?name=chengzw
JS summary

Method: GET
HTTP version: 1.1
Host: localhost:8000
Remote Address: 127.0.0.1
URI: /summary
Headers:
  header 'User-Agent' is 'curl/7.29.0'
  header 'Host' is 'localhost:8000'
  header 'Accept' is '*/*'
Args:
  arg 'name' is 'chengzw'
  

动态模块

Nginx 在 Nginx 1.9.11(release at 2016-02-09) 版本中新增了动态模块(Dynamic Module) 的支持。动态模块在第一次通过 ./configure --add-dynamic-module 编译后,之后如果要对动态模块进行升级,只需要重新编译动态模块,然后替换在 modules 目录内该动态模块的 .so 文件即可,无需替换 Nginx 二进制文件。

下面还是以 njs 模块的例子来演示如何添加动态模块。使用 --add-dynamic-module 指令以动态模块的方式添加 njs 模块:

代码语言:javascript
复制
./configure --with-debug --with-stream --prefix=/usr/local/nginx --with-http_ssl_module --add-dynamic-module=/root/njs/nginx

# 编译
make

编译完成后注意不需要执行 make install 命令,只需要将生成的 Nginx 二进制文件覆盖原先的二进制文件即可。

代码语言:javascript
复制
cp /root/nginx-1.14.2/objs/nginx /usr/local/nginx/sbin/nginx -f

如果是使用 make install 命令安装,会自动在 Nginx 运行目录下创建 modules 目录,这里我们需要手动创建 modules 目录:

代码语言:javascript
复制
mkdir /usr/local/nginx/modules

查看 modules 目录,可以看到有 http 和 stream 两个 njs 模块对应的文件:

代码语言:javascript
复制
[root@nginx-plus1 nginx]# ll /usr/local/nginx/modules
total 7392
-rwxr-xr-x 1 root root 3825792 Jun 11 08:03 ngx_http_js_module.so
-rwxr-xr-x 1 root root 3738944 Jun 11 08:03 ngx_stream_js_module.so

使用 load_module 指令加载动态模块,注意 load_module 指令必须在所有 block (包括 events、http、stream、mail)之前使用:

代码语言:javascript
复制
# 加载动态模块
load_module modules/ngx_http_js_module.so;
load_module modules/ngx_stream_js_module.so;

events {}
http {
    #导入js文件
    js_import js/http.js;

    #设置变量,变量值为调用js文件的相应函数的返回值
    js_set $foo     http.foo;
    js_set $summary http.summary;

    server {
        listen 8000;

        location / {
            add_header X-Foo $foo; #将变量foo的结果添加到响应头中 
            js_content http.baz; #执行其中 JS 内容并输出
        }

        location = /summary {
            return 200 $summary;  #返回变量summary的结果
        }
    }
}

自定义 HTTP 模块

我们定义一个简单的 HTTP 模块,当客户端发送请求时,返回 Hello World!

模块开发有以下步骤:

  • 1.编写模块基本结构。包括模块配置项,模块上下文,模块配置信息。
  • 2.实现 handler 的挂载函数。根据模块的需求选择正确的挂载方式。
  • 3.编写 handler 处理函数。模块的功能主要通过这个函数来完成。
  • 4.编写编译的文件 config。

定义模块配置项

模块配置项 ngx_command_s 结构体定义在 src/core/ngx_conf_file.h 文件中,其结构如下所示:

代码语言:javascript
复制
struct ngx_command_s {
    // 配置项的名称,例如gzip
    ngx_str_t             name;
    
    // 配置项类型,type将指定配置项可以出现的位置。例如出现在location{}或者server{}中,
    // 以及它可以携带的参数个数
    ngx_uint_t            type;
    
    // 出现了name中定义的配置项后,将会调用该方法处理配置项的参数
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    
    //该字段被NGX_HTTP_MODULE类型模块所用 (我们编写的基本上都是NGX_HTTP_MOUDLE,只有一些nginx核心模块是非NGX_HTTP_MODULE),
    // 该字段指定当前配置项存储的内存位置。实际上是使用哪个内存池的问题。因为http模块对所有http模块所要保存的配置信息,
    // 划分了main, server和location三个地方进行存储,每个地方都有一个内存池用来分配存储这些信息的内存。
    // 这里可能的值为 NGX_HTTP_MAIN_CONF_OFFSET、NGX_HTTP_SRV_CONF_OFFSET或NGX_HTTP_LOC_CONF_OFFSET。当然也可以直接置为0,就是NGX_HTTP_MAIN_CONF_OFFSET。
    ngx_uint_t            conf;
    
    //指定该配置项值的精确存放位置,一般指定为某一个结构体变量的字段偏移。因为对于配置信息的存储,一般我们都是定义个结构体来存储的。
    // 那么比如我们定义了一个结构体A,该项配置的值需要存储到该结构体的b字段。那么在这里就可以填写为offsetof(A, b)。
    // 对于有些配置项,它的值不需要保存或者是需要保存到更为复杂的结构中时,这里可以设置为0。
    ngx_uint_t            offset;
    
    // 该字段存储一个指针。可以指向任何一个在读取配置过程中需要的数据,以便于进行配置读取的处理。
    // 大多数时候,都不需要,所以简单地设为NULL即可。
    void                 *post;
};

我们定义了一个配置项,该配置项可以出现在 location 配置块中,该配置项没有参数。当在某个配置块中出现 mytest 配置时,Nginx 将会调用 ngx_http_mytest 方法。

代码语言:javascript
复制
/*
定义模块配置项
*/
static ngx_command_t  ngx_http_mytest_commands[] =
{

    {
        ngx_string("mytest"),
        NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
        // 当在某个配置块中出现 mytest 配置时,Nginx 将会调用 ngx_http_mytest 方法。
        ngx_http_mytest,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL
    },

    ngx_null_command //在ngx_http_hello_commands这个数组定义的最后,都要加一个ngx_null_command作为结尾。
};

定义模块上下文

模块上下文 ngx_http_module_t 结构体定义在 src/http/ngx_http_config.h 文件中。这是一个 ngx_http_module_t 类型的静态变量,这个变量实际上是提供一组回调函数指针,这些函数将被 Nginx 在合适的时间进行调用。其结构如下所示:

代码语言:javascript
复制
typedef struct {

    // 解析配置文件前调用
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
    
    // 完成配置文件的解析后调用
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);

    // 当需要创建数据结构用于存储main级别(直属于http{...}块的配置项)的全局配置项时,
    // 可以通过create_main_conf回调方法创建存储全局配置项的结构体
    void       *(*create_main_conf)(ngx_conf_t *cf);
    
    // 常用于初始化main级别配置项
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);
    
    // 当需要创建数据结构用于存储srv级别(直属于server{...}块的配置项)的配置项时,
    // 可以通过create_srv_conf回调方法创建存储srv级别配置项的结构体
    void       *(*create_srv_conf)(ngx_conf_t *cf);
    
    // 主要用于合并main级别和srv级别下同名的配置项
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
     
    // 当需要创建数据结构用于存储loc级别(直属于location{...}块的配置项)的配置项时,
    // 可以通过create_loc_conf回调方法创建存储loc级别配置项的结构体
    void       *(*create_loc_conf)(ngx_conf_t *cf);
    
    // 主要用于合并srv级别和loc级别下同名的配置项
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

HTTP 框架在读取,重载配置文件时定义了由 ngx_http_module_t 接口描述的 8 个阶段,HTTP 框架在启动过程中会在每个阶段中调用 ngx_http_module_t 中相应的方法。当然,如果 ngx_http_module_t 中的某个回调方法设置为 NULL 空指针时,那么 HTTP 框架是不会调用它的。

本例中我们没有实现 HTTP 框架初始化时会调用的 ngx_http_module_t 中的 8 个方法。

代码语言:javascript
复制
/*
定义模块上下文
*/
static ngx_http_module_t  ngx_http_mytest_module_ctx =
{
    NULL,                              /* preconfiguration */
    NULL,                    /* postconfiguration */

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

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

    NULL,          /* create location configuration */
    NULL            /* merge location configuration */
};

定义模块

对于开发一个模块来说,我们都需要定义一个 ngx_module_t 类型的变量来说明这个模块本身的信息,从某种意义上来说,这是这个模块最重要的一个信息,它告诉了 Nginx 这个模块的一些信息,上面定义的配置信息,还有模块上下文信息,都是通过这个结构来告诉 Nginx 系统的,也就是加载模块的上层代码,都需要通过定义的这个结构,来获取这些信息。

代码语言:javascript
复制
#define NGX_MODULE_V1          0, 0, 0, 0, 0, 0, 1
#define NGX_MODULE_V1_PADDING  0, 0, 0, 0, 0, 0, 0, 0

// src/core/ngx_core.h
typedef struct ngx_module_s      ngx_module_t;
// src/core/ngx_conf_file.h
struct ngx_module_s {

    // 下面的 ctx_index,index,spare0,spare1,spare2,spare3,version 变量不需要在定义时赋值,
    // 可以用 Nginx 准备好的宏 NGX_MODULE_V1 来定义,它已经定义好了这7个值
    ngx_uint_t            ctx_index;
    ngx_uint_t            index;

    ngx_uint_t            spare0;
    ngx_uint_t            spare1;
    ngx_uint_t            spare2;
    ngx_uint_t            spare3;

    ngx_uint_t            version;
    
    // 指向模块的上下文结构体
    void                 *ctx;
    
    // 模块配置项
    ngx_command_t        *commands;
    
    // 模块的类型,它与ctx指针是紧密相关的。在官方Nginx中,它的取值范围是以下5种:
    // NGINX_HTTP_MODULE,NGINX_CORE_MODULE,NGINX_CONF_MODULE,NGX_EVENT_MODULE,NGX_MAIL_MODULE
    ngx_uint_t            type;

    // 在Nginx启动、停止的过程中,以下7个函数指针表示有7个执行点会分别调用这7种方法。
    // 如果不需要在Nginx启动或者停止的过程中执行它,就简单设置为NULL即可。
    ngx_int_t           (*init_master)(ngx_log_t *log);
    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);
    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
    void                (*exit_thread)(ngx_cycle_t *cycle);
    void                (*exit_process)(ngx_cycle_t *cycle);
    void                (*exit_master)(ngx_cycle_t *cycle);

    // 以下8个字段为保留字段,目前没有使用,可以使用Nginx提供的NGX_MODULE_V1_PADDING 宏来填充。
    uintptr_t             spare_hook0;
    uintptr_t             spare_hook1;
    uintptr_t             spare_hook2;
    uintptr_t             spare_hook3;
    uintptr_t             spare_hook4;
    uintptr_t             spare_hook5;
    uintptr_t             spare_hook6;
    uintptr_t             spare_hook7;
};

定义 mytest 模块,这样 mytest 模块在编译时将会被加入到 ngx_modules 全局数组中。Nginx 在启动时,会调用所有模块的初始化回调方法,这个例子中我们没有实现它们。

代码语言:javascript
复制
/*
定义模块
*/
ngx_module_t  ngx_http_mytest_module =
{
    NGX_MODULE_V1,
    // 模块上下文
    &ngx_http_mytest_module_ctx,           /* module context */
    // 模块配置项
    ngx_http_mytest_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
};

模块可以提供一些回调函数给 Nginx,当 Nginx 在创建进程线程或者结束进程线程时进行调用。但大多数模块在这些时刻并不需要做什么,所以都简单赋值为 NULL。

定义 handler 挂载函数

在 Nginx 的 ngx_http_phases 中定义了 HTTP 框架对请求进行处理的 11 个阶段,结构体定义在 src/http/ngx_http_core_module.h :

代码语言:javascript
复制
typedef enum {
    // 1.在接收到完整的HTTP头部后读取请求内容的阶段
    NGX_HTTP_POST_READ_PHASE = 0,

    // 2.在将请求的URI与location表达式匹配之前,修改请求的URI的阶段(所谓的重定向)
    NGX_HTTP_SERVER_REWRITE_PHASE,

    // 3.根据请求的URI寻找匹配的location表达式,
    // 这个阶段只能由ngx_http_core_module模块实现,不建议其他HTTP模块重新定义这一阶段的行为
    NGX_HTTP_FIND_CONFIG_PHASE,
    
    // 4.寻找到匹配的location之后再次利用rewrite修改请求的URI
    NGX_HTTP_REWRITE_PHASE,
    
    // 5.这一阶段是用于在rewrite重写URI后,防止错误的nginx.conf配置导致死循环(递归地修改URI)
    // 因此,这一阶段仅由ngx_http_core_module模块处理。目前,控制死循环的方式很简单,首先检查rewrite的次数,
    // 如果一个请求超过10次重定向,就认为进入了死循环,这时候就会向用户返回500,表示服务器内部错误
    NGX_HTTP_POST_REWRITE_PHASE,

    // 6.决定请求访问权限之前,HTTP模块可以介入处理的阶段
    NGX_HTTP_PREACCESS_PHASE,

    // 7.这个阶段用于让HTTP模块判断是否允许这个请求访问Nginx服务器
    NGX_HTTP_ACCESS_PHASE,
    
    // 8.在NGX_HTTP_ACCESS_PHASE阶段中,当HTTP模块的handler处理函数返回不允许访问的错误代码时(实际就是NGX_HTTP_FORBIDDEN或者NGX_HTTP_UNAUTHORIZED),
    // 这里将负责向用户发送拒绝服务的错误响应,因此,这个阶段实际上是用于给NGX_HTTP_ACCESS_PHASE阶段收尾的
    NGX_HTTP_POST_ACCESS_PHASE,

    // 9.这个阶段完全是为了给try_files配置项而设立的,当Nginx请求静态文件资源时,
    // try_files配置项可以使这个请求顺序地访问多个静态文件资源,如果某一次访问失败,则继续访问try_files中指定的下一个静态资源。
    NGX_HTTP_TRY_FILES_PHASE,
    
    // 10.处理HTTP请求内容的阶段,这是大部分HTTP模块最愿意介入的阶段
    NGX_HTTP_CONTENT_PHASE,
    
    // 11.处理完请求后记录日志的阶段
    NGX_HTTP_LOG_PHASE
} ngx_http_phases;

一般情况下,我们自定义的模块,大多数是挂载在 NGX_HTTP_CONTENT_PHASE 阶段的,默认也就是属于这个模块。

有几个阶段是特例,它们没有 Hook 挂载点(也就意味着,在这几个阶段不允许挂载任何第三方处理逻辑),它们仅由 HTTP 框架实现:

  • NGX_HTTP_FIND_CONFIG_PHASE
  • NGX_HTTP_POST_ACCESS_PHASE
  • NGX_HTTP_POST_REWRITE_PHASE
  • NGX_HTTP_TRY_FILES_PHASE

函数的挂载分为两种方式:一种方式就是按处理阶段挂载,以这种方式挂载的 handler 被称为 content phase handlers;另外一种挂载方式就是按需挂载,以这种方式挂载的 handler 被称为 content handler。

按处理阶段挂载

按处理阶段挂载的动作一般是在模块上下文调用的 postconfiguration 函数中。本例介绍按需挂载,这里不展开说明。

按需挂载

当一个请求进来以后,Nginx 从 NGX_HTTP_POST_READ_PHASE 阶段开始依次执行每个阶段中所有 handler。执行到 NGX_HTTP_CONTENT_PHASE 阶段的时候,如果这个 location 有一个对应的 content handler 模块,那么就去执行这个 content handler 模块真正的处理函数。否则继续依次执行 NGX_HTTP_CONTENT_PHASE 阶段中所有 content phase handlers,直到某个函数处理返回 NGX_OK 或者 NGX_ERROR。

换句话说,当某个 location 处理到 NGX_HTTP_CONTENT_PHASE 阶段时,如果有 content handler 模块,那么 NGX_HTTP_CONTENT_PHASE 挂载的所有 content phase handlers 都不会被执行了。

但是使用这个方法挂载上去的 handler 有一个特点是必须在 NGX_HTTP_CONTENT_PHASE 阶段才能执行到。如果你想自己的 handler 在更早的阶段执行,那就不要使用这种挂载方式。

那么在什么情况会使用这种方式来挂载呢?一般情况下,某个模块对某个 location 进行了处理以后,发现符合自己处理的逻辑,而且也没有必要再调用 NGX_HTTP_CONTENT_PHASE 阶段的其它 handler 进行处理的时候,就动态挂载上这个 handler。

本例我们使用按需挂载的方式。当 Nginx 接收完 HTTP 请求的头部信息时,就会调用 HTTP 框架处理请求。在 ngx_http_mytest 方法中,我们定义了请求的处理方法为 ngx_http_mytest_handler,

代码语言:javascript
复制
/*
按需挂载,ngx_http_mytest_handler方法是真正处理请求的方法
*/
static char * ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    //首先找到mytest配置项所属的配置块,clcf貌似是location块内的数据
//结构,其实不然,它可以是main、srv或者loc级别配置项,也就是说在每个
//http{}和server{}内也都有一个ngx_http_core_loc_conf_t结构体
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

    //http框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时,如果
//请求的主机域名、URI与mytest配置项所在的配置块相匹配,就将调用我们
//实现的ngx_http_mytest_handler方法处理这个请求
    clcf->handler = ngx_http_mytest_handler;

    return NGX_CONF_OK;
}

定义 handler 处理函数

当出现 mytest 配置项时,ngx_http_mytest 方法会被调用,这时将 ngx_http_core_loc_conf_t 结构的 handler 成员指定为 ngx_http_mytest_handler,另外,HTTP 框架在接收完 HTTP 请求的头部后,会调用 handler 挂载函数指向的 handler 处理函数。下面看一下 handler 成员的原型 ngx_http_handler_pr:

代码语言:javascript
复制
// src/http/ngx_http_request.h
typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);

从上面这段代码可以看出,实际处理请求的方法 ngx_http_mytest_handler 在接收一个 ngx_http_request_t 类型的参数 r,返回一个 ngx_int_t 类型的结果。

代码语言:javascript
复制
/*
定义handler处理函数,是真正处理请求的方法
*/
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);
}

定义 config 文件

config 文件其实就是一个可执行的 Shell 脚本。如果只想开发一个 HTTP 模块,那么 config 文件中需要定义以下 3 个变量:

  • ngx_addon_name:仅在configure 执行时使用,一般设置为模块名称。
  • HTTP_MODULES:保存所有的 HTTP 模块名称,每个 HTTP 模块间由空格符相连。在重新设置 HTTP_MODULES 变量时,不要直接覆盖它,因为 configure 调用到自定义的 config 脚本前,已经将各个 HTTP 模块设置到 HTTP_MODULES 变量中了。
  • NGX_ADDON_SRCS:用于指定新增模块的源代码,多个待编译的源代码间以空格符相连。注意:在设置 NGX_ADDON_SRCS 时可以使用 $ngx_addon_dir 变量,它等价于 configure 时执行 --add-module=PATH 的 PATH 参数。
代码语言:javascript
复制
ngx_addon_name=ngx_http_mytest_module
HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"

编译和使用模块

将模块源代码文件 ngx_http_mytest_module.c 和 config 文件放在以下目录中:

代码语言:javascript
复制
[root@nginx-plus1 ~]# ll /root/mymodules/mytest/
total 12
-rw-r--r-- 1 root root  164 Jun 16 10:20 config
-rw-r--r-- 1 root root 4567 Jun 16 10:45 ngx_http_mytest_module.c

编译时使用 --add-module 指令添加我们自定义的模块,这里使用 make install 重新安装 Nginx:

代码语言:javascript
复制
cd /root/nginx-1.14.2/
./configure --prefix=/usr/local/nginx --add-module=/root/mymodules/mytest
# 删除原先的Nginx文件
rm -rm /usr/local/nginx
# 编译
make
# 安装
make install 

编辑 Nginx 配置文件 /usr/local/nginx/conf/nginx.conf:

代码语言:javascript
复制
worker_processes  1;

events {
    worker_connections  1024;
}


http {
    server {
        listen       80;
        location / {
            mytest;  #使用我们自定义的配置项
        }
    }
}

启动 Nginx:

代码语言:javascript
复制
/usr/local/nginx/sbin/nginx

客户端请求,可以看到响应了我们自定义的内容:

代码语言:javascript
复制
[root@nginx-plus1 ~]# curl localhost
Hello World!

参考链接

  • http://tengine.taobao.org/book/chapter_03.html
  • https://tangocc.github.io/2018/07/05/nginx-modul-myself/
  • 深入理解 Nginx 模块开发与结构解析
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-06-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Se7en的架构笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 添加模块
  • 动态模块
  • 自定义 HTTP 模块
    • 定义模块配置项
      • 定义模块上下文
        • 定义模块
          • 定义 handler 挂载函数
            • 定义 handler 处理函数
              • 定义 config 文件
                • 编译和使用模块
                • 参考链接
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档