Nginx基础教程(31)Nginx HTTP框架综述之框架简介:Nginx HTTP框架探秘:从入门到精通

在互联网技术飞速发展的今天,Nginx已成为全球最流行的Web服务器之一,它背后的HTTP框架设计精妙,是支撑其高性能的关键。

1 引言:Nginx的非凡之处

Nginx(发音为“engine x”)是一个HTTP Web服务器、反向代理、内容缓存、负载均衡器、TCP/UDP代理服务器和邮件代理服务器,最初由Igor Sysoev编写。

Nginx以其灵活性和高性能以及低资源利用率而闻名,它是世界上最流行的Web服务器,始终是最受欢迎的Docker镜像之一,并为多个Kubernetes入口控制器提供支持。

那么,是什么让Nginx如此强大?答案就藏在它的模块化架构和精巧的HTTP框架设计中。

想象一下,Nginx就像一个现代化的餐厅:有专门迎宾的接待员(监听端口)、点餐的服务员(接收请求)、后台厨师(处理请求)和传菜员(发送响应)。每个人各司其职,协同工作,从而实现了高效运营。

2 Nginx基础:模块化设计

高度模块化的设计是Nginx的架构基础。Nginx服务器被分解为多个模块,每个模块就是一个功能模块,只负责自身的功能,模块之间严格遵循“高内聚,低耦合”的原则。

2.1 五大功能模块

Nginx内部由核心模块和其他功能模块组成,这种简单的结构分层便于进行功能的扩展,代码更加清晰易于维护。我们通常将其分为五大模块:

  • 核心模块:是Nginx服务器正常运行必不可少的模块,提供错误日志记录、配置文件解析、事件驱动机制、进程管理等核心功能。就像是餐厅的总经理,负责整体运营管理。
  • 标准HTTP模块:提供HTTP协议解析相关的功能,如端口配置、网页编码设置、HTTP响应头设置等。如同餐厅的标准服务流程,确保基本服务规范。
  • 可选HTTP模块:主要用于扩展标准的HTTP功能,让Nginx能处理一些特殊的服务,如Flash多媒体传输、解析GeoIP请求、SSL支持等。好比餐厅的特色服务,如生日庆祝、特殊餐饮需求等。
  • 邮件服务模块:主要用于支持Nginx的邮件服务,包括对POP3协议、IMAP协议和SMTP协议的支持。可以比作餐厅的外卖配送部门,处理外部订单。
  • 第三方模块:为了扩展Nginx服务器应用,完成开发者自定义功能。如同餐厅引进的特色厨师或特色菜品,丰富服务内容。

这种模块化设计就像是乐高积木,每个模块都有特定功能,可以按需组合,构建出强大而灵活的系统。

3 Nginx HTTP框架简介

3.1 HTTP框架的构成

Nginx的HTTP框架是由core模块ngx_http_modulehttp模块ngx_http_core_module共同定义的。

ngx_http_module定义了指令http,保存和管理各个层次里所有http模块的配置数据;而ngx_http_core_module则是http模块的“核心”模块,它定义了listen、server、location等http核心指令,搭建了Nginx的HTTP处理框架。

3.2 HTTP模块分类

在Nginx的HTTP框架中,模块按照功能可以分为四种类型:

  • handler模块:直接处理客户端的请求,产生响应数据,是最常用的模块。好比餐厅中直接制作菜品的厨师
  • filter模块:对handler模块产生的数据做各种加工过滤处理。如同餐厅的摆盘师傅,让菜品更加美观。
  • upstream模块:实现反向代理功能,转发请求到上游服务器,从后端获取响应数据再发回给客户端。就像餐厅的外购专员,当某些食材缺货时去其他市场采购。
  • load-balance模块:不直接处理数据,而是实现负载均衡算法,从upstream块的配置里选择一个合适的上游服务器。如同餐厅的调度员,决定由哪位外购专员去采购。

4 Nginx的架构设计

4.1 Master-Worker多进程模式

Nginx服务器使用master/worker多进程模式。多线程启动和执行的流程如下:主程序Master process启动后,通过一个for循环来接收和处理外部信号;主进程通过fork()函数产生子进程,每个子进程执行一个for循环来实现Nginx服务器对事件的接收和处理。

  • Master进程:也叫监控进程,主要监听外部请求和管理Worker子进程,还有负责更换日志文件、配置读取、平滑升级等。如同餐厅的店长,不直接服务客人,但管理所有服务员。
  • Worker进程:也叫工作进程,负责与后端服务器通信,接收处理结果,Cache和响应客户端请求。好比餐厅的服务员,直接为客人服务。

一般推荐worker进程数与CPU内核数一致,这样一来不存在大量的子进程生成和管理任务,避免了进程之间竞争CPU资源和进程切换的开销。

4.2 异步非阻塞请求处理

Nginx是一个高性能的Web服务器,能够同时处理大量的并发请求。它结合多进程机制和异步机制,异步机制使用的是异步非阻塞方式

每个工作进程使用异步非阻塞方式,可以处理多个客户端请求。当某个工作进程接收到客户端的请求以后,调用IO进行处理,如果不能立即得到结果,就去处理其他的请求(即为非阻塞);而客户端在此期间也无需等待响应,可以去处理其他事情(即为异步);当IO返回时,就会通知此工作进程;该进程得到通知,暂时挂起当前处理的事务去响应客户端请求。

这就像餐厅的高效服务员,不会等一道菜做完才服务下一桌,而是同时照顾多桌客人,厨房的菜好了再去取。

5 Nginx HTTP请求处理流程

如果我们忽略Nginx的异步机制,把注意力集中在处理逻辑上,那么可以看到Nginx的主处理流程与基本的HTTP处理流程区别并不大,它的工作流程如下:

  1. 监听端口,设置回调为ngx_http_init_connection();
  2. 接受客户端连接,调用ngx_http_wait_request_handler();
  3. 调用ngx_http_create_request()创建请求对象
  4. 接收数据,调用ngx_http_process_request_line()解析请求行;
  5. 请求行接收完毕,继续接收数据,调用ngx_http_process_request_headers()解析请求头
  6. 请求头接收完毕,调用ngx_http_process_request()设置异步读写事件;
  7. 调用ngx_http_handler()开始真正处理请求
  8. 调用ngx_http_core_run_phases()按阶段处理请求。这是HTTP框架的核心部分,大部分http模块都在这里运行,最终产生响应数据;
  9. 调用ngx_http_send_header()发送响应头,从函数指针ngx_http_top_header_filter开始,通过header filter模块链过滤处理,最终发送处理过的响应头;
  10. 调用ngx_http_output_filter()发送响应体,从函数指针ngx_http_top_body_filter开始,通过body filter模块链过滤处理,最终发送处理过的响应体;
  11. 处理完毕,记录访问日志。

可以将这个过程比作医院的诊疗流程:挂号(接收连接)、分诊(解析请求)、医生诊断(处理请求)、开药方(生成响应)、取药(发送响应)、病历归档(记录日志)。

6 完整示例:编写自定义HTTP模块

6.1 开发HTTP模块流程

开发一个完整的简单的HTTP模块需要下面几个步骤(以模块名为ngx_http_mytest_module为例):

  1. 编写config文件(这是为了让nginx在configure过程能找到编写的模块)
  2. 编写模块结构 ngx_http_mytest_module
  3. 编写模块上下文结构 ngx_http_mytest_module_ctx
  4. 编写模块命令结构 ngx_http_mytest_commands
  5. 触发命令的回调函数 ngx_http_mytest
  6. 对http请求的具体处理方法 ngx_http_mytest_handler

6.2 示例代码实现

6.2.1 编写config文件
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"

HTTP_MODULES是设置HTTP需要加载的模块列表,在具体编译的时候会生成modules的数组,然后根据数组的先后顺序一个一个加载。

6.2.2 编写模块结构
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r);

// 定义模块命令
static ngx_command_t ngx_http_mytest_commands[] = {
    {
        ngx_string("mytest"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS,
        ngx_http_mytest,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL
    },
    ngx_null_command
};

// 定义模块上下文
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 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
};
6.2.3 编写命令处理函数
// 命令回调函数
static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t *clcf;
    
    // 找到location配置
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    
    // 设置处理函数
    clcf->handler = ngx_http_mytest_handler;
    
    return NGX_CONF_OK;
}
6.2.4 编写请求处理函数
// 请求处理函数
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
    ngx_buf_t *b;
    ngx_chain_t out;
    ngx_int_t rc;
    u_char html_content[] = "<html><body><h1>Hello from mytest module!</h1></body></html>";
    
    // 只支持GET和HEAD方法
    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
        return NGX_HTTP_NOT_ALLOWED;
    }
    
    // 丢弃请求体
    rc = ngx_http_discard_request_body(r);
    if (rc != NGX_OK) {
        return rc;
    }
    
    // 设置响应头
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = sizeof(html_content) - 1;
    r->headers_out.content_type.len = sizeof("text/html") - 1;
    r->headers_out.content_type.data = (u_char *) "text/html";
    
    // 发送响应头
    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }
    
    // 如果有请求体,准备发送
    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    
    out.buf = b;
    out.next = NULL;
    
    b->pos = html_content;
    b->last = html_content + sizeof(html_content) - 1;
    b->memory = 1;
    b->last_buf = (r == r->main) ? 1 : 0;
    b->last_in_chain = 1;
    
    // 发送响应体
    return ngx_http_output_filter(r, &out);
}

6.3 编译和配置

6.3.1 编译Nginx

将上面的C代码保存为ngx_http_mytest_module.c,config文件保存为config,然后编译Nginx:

./configure --add-module=/path/to/your/module
make
make install
6.3.2 配置Nginx

在Nginx配置文件中添加:

server {
    listen 80;
    server_name localhost;
    
    location /test {
        mytest;
    }
}
6.3.3 测试模块

重启Nginx后,访问http://your-server/test,你会看到显示"Hello from mytest module!"。

这个简单的示例展示了Nginx HTTP模块的基本结构。就像学会了做一个简单的菜,虽然简单,但包含了烹饪的基本步骤。

7 Nginx配置实例

7.1 常用指令详解

7.1.1 error_page指令

定义状态码跳转页面。状态码必须在300和599之间。

语法:error_page code ... [=[response]] uri;

常见用法:

error_page 500 502 503 504 /50x.html;
# 将404状态码改成200,并返回empty.gif
error_page 404 =200 /empty.gif;

# 使用变量的方式
location / {
    error_page 404 = @fallback;
}

location @fallback {
    proxy_pass http://backend;
}

# 使用url进行重定向
error_page 403 http://example.com/forbidden.html;
7.1.2 log_format指令

指定日志格式,可以定义多个,但是名称不能一样。

语法:log_format name [escape=default|json|none] string ...;

示例:

log_format test '"客户端地址:"$remote_addr ";客户端名称:" $remote_user'
                '";请求协议:" $request ";请求地址:" $http_host ";请求状态:" $status'
                '";浏览器信息:" $http_user_agent "===" $bytes_sent "===" $connection_requests "===" $request_length:"===" $request_time';

access_log logs/test.log test;
7.1.3 upstream指令

定义一组server。

语法:upstream name { ... }

示例:

upstream backend {
    server backend1.example.com weight=5;
    server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server unix:/tmp/backend3;
    
    server backup1.example.com backup;
}

接收到请求会进行轮询访问每个节点,如果其中一个节点宕机会尝试下一个节点,直到最后一个地址或者最后一个错误地址的通信信息。

参数说明:

  • weight:配置权重,默认是1
  • max_fails:失败几次后会被剔除
  • fail_timeout:失败重试时间
  • backup:备用机(其他服务挂掉之后,才会被访问)
  • down:标识服务器节点不可用

7.2 完整配置示例

下面是一个综合配置示例,展示了Nginx作为反向代理和负载均衡器的典型用法:

# 工作进程配置
worker_processes auto;

# 错误日志配置
error_log /var/log/nginx/error.log warn;

# 事件模块配置
events {
    worker_connections 1024;
    use epoll;
}

# HTTP模块配置
http {
    # 基本设置
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    # 日志格式
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
    
    log_format detailed '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" "$http_x_forwarded_for" '
                        'rt=$request_time uct="$upstream_connect_time" '
                        'uht="$upstream_header_time" urt="$upstream_response_time"';
    
    # 访问日志
    access_log /var/log/nginx/access.log main;
    
    # 优化设置
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    
    # Gzip压缩
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    
    # 上游服务器配置
    upstream backend {
        # 负载均衡方法
        least_conn;
        
        server 10.0.0.1:8080 weight=3 max_fails=3 fail_timeout=30s;
        server 10.0.0.2:8080 weight=2 max_fails=3 fail_timeout=30s;
        server 10.0.0.3:8080 weight=1 max_fails=3 fail_timeout=30s;
        server 10.0.0.4:8080 backup;
    }
    
    # 服务器配置
    server {
        listen 80;
        server_name example.com;
        
        # 根位置配置
        location / {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            
            # 超时设置
            proxy_connect_timeout 5s;
            proxy_send_timeout 10s;
            proxy_read_timeout 10s;
        }
        
        # 健康检查
        location /health {
            access_log off;
            return 200 "healthy\n";
            add_header Content-Type text/plain;
        }
        
        # 静态文件服务
        location /static/ {
            alias /var/www/static/;
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
        
        # API接口
        location /api/ {
            proxy_pass http://backend/api/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            
            # CORS设置
            add_header Access-Control-Allow-Origin *;
            add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, DELETE";
            add_header Access-Control-Allow-Headers "Authorization, Content-Type";
            
            # 限制请求大小
            client_max_body_size 10m;
        }
        
        # 错误页面
        error_page 404 /404.html;
        location = /404.html {
            internal;
        }
        
        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            internal;
        }
    }
    
    # 第二个虚拟主机
    server {
        listen 443 ssl http2;
        server_name secure.example.com;
        
        ssl_certificate /etc/ssl/certs/example.com.crt;
        ssl_certificate_key /etc/ssl/private/example.com.key;
        
        # 安全设置
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
        ssl_prefer_server_ciphers off;
        
        location / {
            root /var/www/secure;
            index index.html;
            
            # 安全头
            add_header X-Frame-Options DENY;
            add_header X-Content-Type-Options nosniff;
            add_header X-XSS-Protection "1; mode=block";
            add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
        }
    }
}

这个配置就像是一个餐厅的完整运营手册,包含了从接待客人(监听端口)、菜单设计(location配置)、厨师团队(upstream配置)到服务标准(超时设置、缓存策略)等各个方面。

8 总结

Nginx的HTTP框架是一个经过精心设计的、高度模块化的系统,它通过事件驱动模型异步非阻塞处理方式实现了高性能。其Master-Worker多进程模式既保证了稳定性,又充分利用了现代多核CPU的计算能力。

通过本文的介绍,我们了解了:

  1. Nginx的模块化设计,包括五大功能模块;
  2. HTTP框架的基本构成和四种HTTP模块类型;
  3. Nginx的请求处理流程
  4. 如何编写自定义HTTP模块
  5. Nginx的常用指令和配置方法

Nginx就像一个瑞士军刀,看似简单,但内含多种功能,只要掌握得当,就能应对各种复杂的网络场景。

学习Nginx HTTP框架就像学习烹饪,开始时可能只会煮方便面(配置简单服务器),随着经验的积累,逐步能做出满汉全席(配置复杂的反向代理、负载均衡集群)。希望本文能为你的Nginx学习之旅打下良好基础!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值