Nginx的基础架构解析(下)

1. Nginx模块

1.1 Nginx中的模块化设计

Nginx 的内部结构是由核心部分和一系列的功能模块所组成。这样划分是为了使得每个模块的功能相对简单,便于开发,同时也便于对系统进行功能扩展。Nginx 将各功能模块组织成一条链,当有请求到达的时候,请求依次经过这条链上的部分或者全部模块,进行处理。例如前面讲到的 http 请求,会有11个处理阶段,而每个阶段有对应着许多在此阶段生效的模块对该 http 请求进行处理。同时,Nginx 开放了第三方模块编写功能,用户可以自定义模块,控制 http 请求的处理与响应,这种高度可定制化催生了 Nginx 的大量第三方模块,也使得 Nginx 定制化开发在各大互联网公司十分流行。

1.2 Nginx中的模块分类

Nginx 模块的分类有很多种方式,目前网上博客中写的较多的是按照功能进行分类,有如下几大类:

event 模块: 搭建 独立于操作系统的事件处理机制的框架,以及 提供各种具体事件的处理。代表性的模块有:ngx_events_module, ngx_event_core_module, ngx_epoll_module;

handler 模块: 主要负责处理客户端请求并产生待响应的内容,比如 ngx_http_static_module 模块,负责客户端的静态页面请求处理并将对应的磁盘 文件准备为响应内容输出;

filter 模块: 主要 负责处理输出的内容,包括修改输出内容。代表性的模块有: ngx_http_sub_module;

upstream 模块: 该类模块都是用于实现反向代理功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回给客户端。比如前面介绍到转发 http、websocket、grpc、rtmp等协议的模块都可以划分为这一类;

负载均衡模块: 负载均衡的模块,实现相应算法。这类模块都是用于实现 Nginx 的负载均衡功能。

extend 模块: 又称第三方模块,非 Nginx 官方提供,由各大企业的开发人员结合自身业务开发而成。Nginx 提供了非常好的模块编写机制,遵循相关的标准可以很快定制出符合我们业务场景的模块,而且内部调用 Nginx 内部提供的方法进行处理,使得第三方模块往往都具备很好的性能

进入 Nginx 的源码目录,使用ls查看源码文件,限速模块是在 http 目录中的。

[root@server nginx-1.17.6]# cd src/

[root@server src]# ls
core  event  http  mail  misc  os  stream

[root@server src]# ls http/modules/ngx_http_limit_*.c
http/modules/ngx_http_limit_conn_module.c
http/modules/ngx_http_limit_req_module.c

Nginx 模块对应的代码文件后,我们就可以阅读里面的代码进行学习。往往源码的阅读是枯燥无味的,我们可以借助海量的网络资源辅助我们学习。
1.3 如何学习和使用第三方模块

Openresty 社区提供了一款 Nginx 中的 Echo 模块,即echo-nginx-module。在 Nginx 中添加了该模块后,我们在配置文件中可以使用该模块提供的 echo 指令返回用户响应,简单方便。该模块的源码在 github 上,并且有良好的文档和使用示例,非常方便开发者使用。

在这里插入图片描述

现在我们在 Nginx 的源码编译阶段加入该第三方模块,具体操作如下:

[root@server shencong]# pwd
/root/shencong
[root@server shencong]# mkdir nginx-echo
# 下载 nginx 源码包和第三方模块的源码包 
[root@server shencong]# wget http://nginx.org/download/nginx-1.17.6.tar.gz
[root@server shencong]# wget https://github.com/openresty/echo-nginx-module/archive/v0.62rc1.tar.gz

# 解压
[root@server shencong]# tar -xzf nginx-1.17.6.tar.gz
[root@server shencong]# tar -xzf v0.62rc1.tar.gz

[root@server shencong]# ls
echo-nginx-module-0.62rc1  nginx-1.17.6  nginx-1.17.6.tar.gz  nginx-echo  v0.62rc1.tar.gz

[root@server shencong]# cd nginx-1.17.6
# 使用--add-module添加第三方模块,参数为第三方模块源码
[root@server shencong]# ./configure --prefix=/root/shencong/nginx-echo  --add-module=/root/shencong/echo-nginx-module-0.62rc1

编译完成后,我们就可以去nginx-echo目录中的 nginx.conf文件中添加echo 指令 。准备如下的配置(可以参参考社区提供的示例):

...
http {
   server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }
        
        # 新增测试 echo 指令配置
        location /timed_hello {
            default_type text/plain;
            echo_reset_timer;
            echo hello world;
            echo "'hello world' takes about $echo_timer_elapsed sec.";
            echo hiya igor;
            echo "'hiya igor' takes about $echo_timer_elapsed sec.";
        }

        location /echo_with_sleep {
            default_type text/plain;
            echo hello world;
            echo_flush;  # ensure the client can see previous output immediately
            echo_sleep   2.5;  # in sec
            echo "'hello' takes about $echo_timer_elapsed sec.";
        }

    }
}
...

启动 Nginx 后,我们就可以在浏览器上请求者两个 URI 地址,看到相应 echo 返回的信息了。第二个配置是使用了 echo_sleep 指令,会使得请求在休眠 2.5s 后才返回。
在这里插入图片描述

1.4 如何编写自己的模块

想要编写 Nginx 模块,首先需要对 Nginx 模块中的源码以及相关的数据结构有所了解,还要知晓 Nginx HTTP 模块的调用流程。假设我要实现前面第三方模块Echo的最简单形式,即只输出相应的字符串即可。假定模块支持的指令名称还是 echo, 这个 echo 指令需要跟一个参数,即输出的字符串。我们需要做如下几步:

确定模块名称,以及模块中的指令以及参数,还有运行的环境。这里涉及的结构是 ngx_command_t,它定义了模块里的所有指令格式。下面的代码表示该模块中只有一个 echo 指令,它出现的上下文环境为 location,且有一个参数(NGX_CONF_TAKE1)。当某个配置块中出现echo指令时,Nginx 将调用ngx_http_echo方法。然后在该方法中,会设置处理请求的 handler,这个 handler 就是处理请求的方法。

  static ngx_command_t  ngx_http_echo_commands[] = {  
         { ngx_string("echo"),      /* 指令名称,利用ngx_string宏定义 */
          NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,  /* 用在 location 指令块内,且有1个参数 */
          ngx_http_echo,            /* 处理回调函数 */
          NGX_HTTP_LOC_CONF_OFFSET,    
          offsetof(ngx_http_echo_loc_conf_t, ed), /* 指定参数读取位置 */
          NULL },
          ngx_null_command
  };

完成请求处理的 handler 函数,最重要的部分就在这里;

 static char *
 ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
     ngx_http_core_loc_conf_t  *clcf;
     /* 找到指令所属的配置块,这里我们限定echo指令的上下文环境只有location */
     clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
     /* 指定处理的handler */
     clcf->handler = ngx_http_echo_handler;
     ...
     return NGX_CONF_OK;
 }
 
 static ngx_int_t
 ngx_http_echo_handler(ngx_http_request_t *r)
 {
     ...
     
     /* 向用户发送相应包 */
     return ngx_http_output_filter(r, &out);
 }

比如配置模块介入 http 请求的哪些阶段等。

 /* Http context of the module */
  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 */
      ngx_http_echo_create_loc_conf,         /* create location configration */
      ngx_http_echo_merge_loc_conf           /* merge location configration */
  };
  /* Module */
  ngx_module_t  ngx_http_echo_module = {
      NGX_MODULE_V1,
      &ngx_http_echo_module_ctx,             /* module context */
      ngx_http_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
  };

2. 案例

首先新建一个目录echo-nginx-module,然后在目录下新建两个文件config和ngx_http_echo_module.c

[root@server echo-nginx-module]# pwd
/root/shencong/echo-nginx-module

[root@server echo-nginx-module]# ls
config  ngx_http_echo_module.c

两个文件内容分别如下:

[root@server echo-nginx-module]# cat 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"

[root@server echo-nginx-module]# cat ngx_http_echo_module.c
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

/* Module config */
typedef struct {
    ngx_str_t  ed;
} 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);

/* 定义指令 */
static ngx_command_t  ngx_http_echo_commands[] = {
    { ngx_string("echo"),      /* 指令名称,利用ngx_string宏定义 */
        NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,  /* 用在 location 指令块内,且有1个参数 */
        ngx_http_echo,         /* 处理回调函数 */
        NGX_HTTP_LOC_CONF_OFFSET,    
        offsetof(ngx_http_echo_loc_conf_t, ed), /* 指定参数读取位置 */
        NULL },
        ngx_null_command
};
/* Http context of the module */
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 */
    ngx_http_echo_create_loc_conf,         /* create location configration */
    ngx_http_echo_merge_loc_conf           /* merge location configration */
};
/* Module */
ngx_module_t  ngx_http_echo_module = {
    NGX_MODULE_V1,
    &ngx_http_echo_module_ctx,             /* module context */
    ngx_http_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
};
/* Handler function */
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)))
    {
        /* 如果不是 HEAD/GET/PUT 请求,则返回405 Not Allowed错误 */
        return NGX_HTTP_NOT_ALLOWED;
    }
    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;
    if(r->method == NGX_HTTP_HEAD)
    {
        rc = ngx_http_send_header(r);
        if(rc != NGX_OK)
        {
            return rc;
        }
    }
    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;
    b->last_buf = 1;
    rc = ngx_http_send_header(r);
    if(rc != NGX_OK)
    {
        return rc;
    }
    /* 向用户发送相应包 */
    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);
    /* 指定处理的handler */
    clcf->handler = ngx_http_echo_handler;
    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;
    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;
}
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(conf->ed, prev->ed, "");
    return NGX_CONF_OK;
}

这样一个第三方模块包就完成了,接下来我们要向之前使用第三方模块一样,将它编译进 Nginx,具体操作如下。

[root@server shencong]# cd nginx-1.17.6/
[root@server nginx-1.17.6]# ./configure --prefix=/root/shencong/nginx-echo --add-module=/root/shencong/echo-nginx-module
...
[root@server nginx-1.17.6] # make && make install
...
[root@server nginx-1.17.6]# cd ../nginx-echo/sbin/
[root@server sbin]# ./nginx -V
nginx version: nginx/1.17.6
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC) 
configure arguments: --prefix=/root/shencong/nginx-echo --add-module=/root/shencong/echo-nginx-module

接下来,我们只要在 nginx.conf 中加入我们的指令,并给一个参数,就能看到我们自定义的输出了。

...
http {
   ...
   server {
       listen       80;
       server_name  localhost;
       location / {
            root   html;
            index  index.html index.htm;
       }
       
       location /test {
            echo hello,world;
       }
       ...
   }
}
...

最后我们请求主机的80端口,URI=/test,浏览器输出"hello, world",说明我们的自定义模块成功了!
在这里插入图片描述

3. 小结

在 Nginx 基础架构介绍的最后,主要是介绍了 Nginx 的模块设计以及相应的模块用法。最后,我们简单给出了一个简单编写自己模块的案例作为本章的实战案例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/908100.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【网络】网络层协议IP

目录 IP协议报头 报头分离和向上交付 四位版本 8位服务类型 16位总长度 八位生存时间 16位标识一行 网段划分 DHCP 私有IP范围 公网划分之CIDR 特殊的IP地址 缓解IP地址不够用的方法 NAT技术 路由 IP是用来主机定位和路由选择的&#xff0c;它提供了一种能力&am…

HTML 基础标签——多媒体标签<img>、<object> 与 <embed>

文章目录 1. `<img>` 标签主要属性示例注意事项2. `<object>` 标签概述主要属性示例注意事项3. `<embed>` 标签概述主要属性示例注意事项小结在现代网页设计中,多媒体内容的使用变得越来越重要,因为它能够有效增强用户体验、吸引注意力并传达信息。HTML 提…

【Canal 中间件】Canal 实现 MySQL 增量数据的异步缓存更新

文章目录 一、安装 MySQL1.1 启动 mysql 服务器1.2 开启 Binlog 写入功能1.2.1创建 binlog 配置文件1.2.2 修改配置文件权限1.2.3 挂载配置文件1.2.4 检测 binlog 配置是否成功 1.3 创建账户并授权 二、安装 RocketMQ2.1 创建容器共享网络2.2 启动 NameServer2.3 启动 Broker2.…

深度学习(九):推荐系统的新引擎(9/10)

一、深度学习与推荐系统的融合 深度学习在推荐系统中的融合并非偶然。随着互联网的飞速发展&#xff0c;数据量呈爆炸式增长&#xff0c;传统推荐系统面临着诸多挑战。例如&#xff0c;在处理大规模、高维度的数据时&#xff0c;传统方法往往显得力不从心。而深度学习以其强大的…

masm汇编字符串输出演示

assume cs:code, ds:datadata segmentmassage db zhouzunjie, 0dh, 0ah, $ data endscode segmentstart:mov ax, datamov ds, axmov ah, 09hlea dx, massageint 21hmov ax, 4c00hint 21hcode ends end start 效果演示&#xff1a;

在昇腾Ascend 910B上运行Qwen2.5推理

目前在国产 AI 芯片&#xff0c;例如昇腾 NPU 上运行大模型是一项广泛且迫切的需求&#xff0c;然而当前的生态还远未成熟。从底层芯片的算力性能、计算架构的算子优化&#xff0c;到上层推理框架对各种模型的支持及推理加速&#xff0c;仍有很多需要完善的地方。 今天带来一篇…

HarmonyOS一次开发多端部署三巨头之界面级一多开发

界面级一多开发 引言1. 布局能力1.1 自适应布局1.1.1 拉伸能力1.1.2 均分能力1.1.3 占比能力1.1.4 缩放能力1.1.5延伸能力1.1.6 隐藏能力1.1.7 折行能力 1.2 响应式布局1.2.1 断点和媒体查询1.2.2 栅格布局 2. 视觉风格2.1 分层参数2.2 自定义资源 3. 交互归一4. IDE多设备预览…

(58)LMS自适应滤波算法与系统辨识的MATLAB仿真

文章目录 前言一、LMS算法的基本步骤二、LMS算法的一些主要应用1. 通信系统2. 信号分离与增强3. 控制系统4. 生物医学信号处理5. 机器学习与模式识别6. 其他应用 三、LMS算法用于系统辨识的MATLAB仿真四、仿真结果 前言 LMS&#xff08;Least Mean Squares&#xff0c;最小均方…

bootstrap应用1——计算n从1-100000的每个整数,第j个观测在自助法样本里的概率。

计算n从1-100000的每个整数&#xff0c;第j个观测在自助法样本里的概率。 pr function(n) return(1 - (1 - 1/n)^n) x 1:10000 plot(x, pr(x))

AI-基本概念-向量、矩阵、张量

1 需求 需求&#xff1a;Tensor、NumPy 区别 需求&#xff1a;向量、矩阵、张量 区别 2 接口 3 示例 4 参考资料 【PyTorch】PyTorch基础知识——张量_pytorch张量-CSDN博客

【设计模式】策略模式定义及其实现代码示例

文章目录 一、策略模式1.1 策略模式的定义1.2 策略模式的参与者1.3 策略模式的优点1.4 策略模式的缺点1.5 策略模式的使用场景 二、策略模式简单实现2.1 案例描述2.2 实现代码 三、策略模式的代码优化3.1 优化思路3.2 抽象策略接口3.3 上下文3.4 具体策略实现类3.5 测试 参考资…

2025年PMP考试的3A好考吗?

确实&#xff0c;PMP正式抛弃第六版用第七版教材了&#xff0c;但是考纲还是跟24年一样的&#xff0c;情景题多&#xff0c;考的比之前灵活&#xff0c;但是 3A 的人也不少&#xff0c;按照机构的计划来学习并没有很难&#xff0c;给大家说说我的备考经历吧&#xff0c;希望对你…

VScode + PlatformIO 了解

​Visual Studio Code Visual Studio Code&#xff08;简称 VS Code&#xff09;是一款由微软开发且跨平台的免费源代码编辑器。该软件以扩展的方式支持语法高亮、代码自动补全&#xff08;又称 IntelliSense&#xff09;、代码重构功能&#xff0c;并且内置了工具和 Git 版本…

完美日记营销模式对开源 AI 智能名片 2 + 1 链动模式 S2B2C 商城小程序的启示

摘要&#xff1a;本文通过分析完美日记在营销中利用社会基础设施升级红利、网红与新流量平台、KOL 和私域流量等策略取得成功的案例&#xff0c;探讨其对开源 AI 智能名片 2 1 链动模式 S2B2C 商城小程序在营销推广、用户获取与留存、提升复购率等方面的启示&#xff0c;为商城…

Failed to install Visual Studio Code update

当关闭vsCode的时候&#xff0c;出现了下面的报错&#xff1a; 可能是之前将vscode文件换了位置导致的&#xff0c;并且vscode在桌面的图标也变成了下面这个&#xff1a; 解决方法&#xff1a; 找到上图路径的log文件并打开&#xff1a; 搜索电脑中的Code.exe文件 并粘贴到上…

python在word的页脚插入页码

1、插入简易页码 import win32com.client as win32 from win32com.client import constants import osdoc_app win32.gencache.EnsureDispatch(Word.Application)#打开word应用程序 doc_app.Visible Truedoc doc_app.Documents.Add() footer doc.Sections(1).Footers(cons…

Rust 力扣 - 73. 矩阵置零

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们使用两个变量记录矩阵初始状态的第一行与第一列是否存在0 然后我们遍历矩阵&#xff08;跳过第一行与第一列&#xff09;&#xff0c;如果矩阵中元素为0则将该元素映射到矩阵第一行与矩阵第一列的位置置为0…

Python | Leetcode Python题解之第537题复数乘法

题目&#xff1a; 题解&#xff1a; class Solution:def complexNumberMultiply(self, num1: str, num2: str) -> str:real1, imag1 map(int, num1[:-1].split())real2, imag2 map(int, num2[:-1].split())return f{real1 * real2 - imag1 * imag2}{real1 * imag2 imag1…

tauri开发中如果取消了默认的菜单项,复制黏贴撤销等功能也就没有了,解决办法

取消默认的菜单项&#xff1a;清除tauri默认的菜单项&#xff0c;让顶部的菜单menu不显示-CSDN博客 就是通过配置空菜单&#xff0c;让菜单不显示&#xff0c;但是这个引发的问题就是复制黏贴撤销等功能也就没有了&#xff0c;解决办法&#xff1a; 新增加编辑下的子菜单&…

STM32F103C8T6学习笔记3--按键控制LED灯

1、实验内容 S4、S5分别接PB12和PB13&#xff0c;实验要求&#xff0c;按下S4&#xff0c;D1亮&#xff0c;D2灭&#xff1b;按下S5&#xff0c;D2亮&#xff0c;D1灭。 由于按键学习的是GPIO口的输入功能&#xff0c;和输出功能的配置略有区别。本次通过按键触发相应功能没有…