Nginx基础教程(30)Nginx模块体系之C++开发模块:给Nginx植入“最强大脑”:C++模块开发实战指南

让你的Nginx不再只是流量搬运工,而是变身全能业务高手

引言:Nginx,你究竟还有多少副面孔?

兄弟们,姐妹们,各位在代码海洋里冲浪的弄潮儿们!提到Nginx,你脑子里第一反应是啥?

“哦,那个反向代理,老快了!”
“对对对,负载均衡一把好手!”
“静态资源服务器,YYDS!”

停!打住!如果Nginx在你心中还只是个“傻快”的流量搬运工,那今天这篇文章,就是来给你“洗脑”的。

想象一下,你是一家网红火锅店的老板。Nginx就是你那个腿脚麻利、永不犯错的金牌服务员。客人来了(请求),他看一眼就能精准地分到空闲的桌子(上游服务器)。客人要杯凉白开(静态文件),他秒速送到。

但问题是,如果客人想办张会员卡,查询积分,或者想玩个“掷骰子免单”的小游戏呢?这个服务员就懵了,他得跑回后厨(你的应用服务器,比如Java/Python/PHP那边)去问,一来一回,再麻利的腿脚也耽误工夫。

那么,灵魂拷问来了:能不能让这个金牌服务员,不仅腿脚麻利,还能当场算账、办理会员、甚至陪你掷骰子

答案是:能!必须能! 而实现这一切的魔法,就是——用C++为Nginx开发模块

今天,我们不满足于让Nginx当跑堂,我们要亲手给他植入“最强大脑”,让他成为能文能武、业务通吃的“全能店长”!

第一章:手术前的“知情同意书”——我们到底在干啥?

在开始这场“外科手术”前,咱们得先统一思想,明白我们即将捣鼓的是什么。

1.1 Nginx的“可扩展性”基石

Nginx之所以强大,除了它那经典的事件驱动、异步非阻塞模型,另一个核心就是其高度模块化的设计。它的每一个功能,比如处理HTTP请求、解析Headers、提供Gzip压缩,本质上都是一个模块。

我们这些开发者要做的,就是遵循它的游戏规则,编写一个符合规范的“插件”(也就是模块),然后把它“插入”到Nginx的身体里。从此,Nginx就拥有了你赋予它的超能力。

1.2 为什么是C++?不是Lua?不是JavaScript?

好问题!Nginx社区确实有像lua-nginx-module这样的神器,用脚本语言开发又快又方便。但C/C++的优势在于:

  • 性能巅峰:与Nginx自身同为C语言开发,无缝衔接,性能损耗极低。对于CPU密集型的复杂业务逻辑,C++是终极选择。
  • 深度集成:你可以深入到Nginx的任何一个生命周期,实现任何你想实现的功能,限制你的只有你的C++水平。
  • “系统级”能力:直接操作内存、进行底层网络通信等,自由度拉满。

简单说,Lua像是给Nginx配了个便捷的对讲机,而C++是直接给他做了个脑机接口

1.3 我们的“小目标”

今天,我们不搞那么复杂的。我们来实现一个看起来简单,但“五脏俱全”的模块,名叫nd_hello_world

它的功能是:当用户访问http://你的域名/hello时,Nginx不会去代理别的服务,也不会找磁盘文件,而是直接由我们的C++代码,返回一个经典的"Hello, World! The current time is {当前时间}"

别看功能简单,它将带你走完Nginx模块开发的全流程:配置、编译、安装、测试。

第二章:搭建你的“手术台”——开发环境准备

工欲善其事,必先利其器。我们先得把“手术台”搭起来。

你需要准备:

  • 一台Linux机器(Ubuntu/CentOS都行,我这里以Ubuntu 20.04为例)
  • Nginx源码:对,不是直接用apt-get安装的那个,我们要从源码编译
  • C++编译工具链:g++, make, cmake等
  • 一颗不怕报错的心

步骤一:安装依赖

sudo apt-get update
sudo apt-get install build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev git -y

步骤二:获取Nginx源码

我们去官网下个稳定版,比如1.24.0。

wget https://nginx.org/download/nginx-1.24.0.tar.gz
tar -zxvf nginx-1.24.0.tar.gz
cd nginx-1.24.0

现在,你进入了Nginx的“心脏”地带。

第三章:解剖Nginx模块的“基因序列”

在动刀之前,得先了解Nginx模块长啥样。一个Nginx模块,主要包含两部分:

  • 配置结构:告诉Nginx你这个模块需要什么配置参数。
  • 模块定义:向Nginx系统“注册”你自己,声明你的“姓名”、“身份证号”(唯一标识)以及“技能”(处理函数)。

最关键的是那个处理函数。Nginx在处理一个请求时,会把请求分成很多阶段(Phase),比如NGX_HTTP_POST_READ_PHASENGX_HTTP_CONTENT_PHASE等。我们的模块通常挂在NGX_HTTP_CONTENT_PHASE阶段,因为这个阶段是用来生成内容的。

我们的C++代码,就是要提供一个在这个阶段被调用的函数,在这个函数里,我们生成"Hello, World"并发送给客户端。

第四章:上才艺!手搓你的第一个C++模块

激动人心的时刻到了!我们开始写代码。

第一步:创建我们的模块源码文件

nginx-1.24.0/目录下,我们创建一个新文件src/nd_hello_world_module.cpp。用src文件夹来管理我们的代码是个好习惯。

// src/nd_hello_world_module.cpp
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ctime>
#include <cstring>

// 声明我们的处理函数
static ngx_int_t ngx_http_nd_hello_world_handler(ngx_http_request_t *r);

// 1. 模块配置结构(虽然我们这个简单模块不需要,但结构得有)
static ngx_http_module_t ngx_http_nd_hello_world_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
};

// 2. 模块指令定义 (这是我们这个demo的核心!)
// 这个结构告诉Nginx,当在配置文件中遇到 `nd_hello_world` 这个指令时,该干嘛。
static ngx_command_t ngx_http_nd_hello_world_commands[] = {
    {
        ngx_string("nd_hello_world"), // 指令名字
        NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS, // 使用在location块,且不接受参数
        ngx_http_nd_hello_world_handler, // 当遇到这个指令时,调用的函数!
        0, // 指令相关的配置结构偏移,我们不用
        0, // 同上
        NULL
    },
    ngx_null_command // 数组结束标志
};

// 3. 核心处理函数!
// 当有人请求配置了 `nd_hello_world on;` 的location时,这个函数就会被调用。
static ngx_int_t ngx_http_nd_hello_world_handler(ngx_http_request_t *r) {
    // 我们只处理GET和HEAD方法,像个正经的HTTP处理器
    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;
    }

    // 获取当前时间,让我们的响应更“动态”一点
    std::time_t now = std::time(nullptr);
    std::tm* tm_now = std::localtime(&now);
    char time_str[100];
    std::strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_now);

    // 构造响应内容
    std::string response = "Hello, World! The current time is ";
    response += time_str;
    response += "\\n";

    // 设置响应头
    ngx_str_t content_type = ngx_string("text/plain; charset=utf-8");
    r->headers_out.content_type = content_type;
    r->headers_out.content_type_len = content_type.len;
    r->headers_out.status = NGX_HTTP_OK; // 200 OK
    r->headers_out.content_length_n = response.length();

    // 发送响应头
    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK) {
        return rc;
    }

    // 构造一个缓冲区来存放我们的响应体
    ngx_buf_t *b;
    b = ngx_create_temp_buf(r->pool, response.length());
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    // 把我们的字符串拷贝到缓冲区
    ngx_memcpy(b->pos, response.c_str(), response.length());
    b->last = b->pos + response.length();
    b->last_buf = 1; // 这是最后一个缓冲区

    // 构造输出链
    ngx_chain_t out;
    out.buf = b;
    out.next = NULL;

    // 最终,发送响应体!
    return ngx_http_output_filter(r, &out);
}

// 4. 模块定义 (向Nginx“自我介绍”)
ngx_module_t ngx_http_nd_hello_world_module = {
    NGX_MODULE_V1, // 一个宏,填充了模块结构的版本信息等
    &ngx_http_nd_hello_world_module_ctx, // 模块上下文
    ngx_http_nd_hello_world_commands,    // 模块指令
    NGX_HTTP_MODULE,                     // 模块类型(我们是HTTP模块)
    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:“嘿,兄弟,编译的时候带上我的模块!”

在configure时添加我们的模块:

./configure --add-module=./src
make
sudo make install

注意,由于我们用了C++,需要稍微修改一下Nginx的编译系统,编辑auto/makefileauto/configure文件,确保使用g++而不是gcc来编译我们的模块。

第三步:配置Nginx使用我们的模块

编辑Nginx配置文件(通常是/usr/local/nginx/conf/nginx.conf),添加如下location:

server {
    listen 80;
    server_name localhost;
    
    location /hello {
        nd_hello_world;
    }
}

第四步:测试我们的模块

启动Nginx,然后访问http://你的服务器/hello,你应该能看到:

Hello, World! The current time is 2023-08-01 14:30:45

恭喜你! 你已经成功给Nginx植入了第一个“最强大脑”!

第五章:拒绝C指针恐怖片!用C++优雅“驯服”Nginx

兄弟们,搞过Nginx模块开发的举个手?我仿佛已经看到了你们皱起的眉头和那写满“痛苦”的眼神。没错,Nginx本身是个性能怪兽,但它的开发模式,还停留在那个充满void*、手动内存管理和冗长函数名的“C语言远古时代”。

你是不是也经历过这种绝望:

  • ngx_str_t用起来束手束脚,一不小心就内存越界
  • 那个神秘的ngx_http_request_t *r,像个百宝箱,也是个大坑,里面的字段多到让人怀疑人生
  • 最要命的是,每个资源都得小心翼翼地分配和释放,一个瞌睡,内存泄漏和野指针就来找你聊天了

说好的“编程乐趣”呢?全变成了“调试噩梦”。

别慌!今天,我就是来给大家送“降压药”的。咱们不改变Nginx的内核,而是在它之上,用C++搭建一层“舒适区”。通过设计和运用C++包装类,我们能像在现代都市开自动驾驶汽车一样,安全、舒适地驾驭Nginx这头性能猛兽。

5.1 为什么是C++?给Nginx来一次“代码精装修”

你可能会问:“Nginx用C写得好好的,为啥要折腾C++?”问得好!这就好比问你:“毛坯房也能住,为啥要装修?”

  • RAII:自动化的管家婆 C++的“资源获取即初始化”原则,是我们的王牌。简单说,就是让对象的构造和析构函数去自动管理资源(内存、文件句柄等)。ngx_palloc申请的内存?交给包装类,它析构时自动ngx_pfree。你再也不用在函数的每个错误返回处手动清理了,从此和“忘了释放”说拜拜。
  • 封装:给复杂结构穿上“制服”ngx_str_tngx_list_tngx_table_elt_t(Header)这些“裸奔”的结构体,用类包装起来。给它们加上安全的API,比如.length().c_str(),甚至重载操作符==+,让操作字符串像用STL一样爽。
  • 类型安全:让编译器当你的“保镖” C里面动不动就void*,编译器只能干瞪眼。C++通过强类型和模板,能在编译阶段就抓住很多“张冠李戴”的类型错误,把bug扼杀在摇篮里。
  • 可读性与可维护性:代码是写给人看的 request.getHeader("User-Agent")ngx_http_get_header_field(r, &ngx_http_user_agent),你觉得哪个更清晰?当你的业务逻辑被清晰的C++对象和函数调用包裹,代码的自解释性会极大提升,三个月后你再看,依然能秒懂。

5.2 设计我们的“瑞士军刀”——核心包装类蓝图

理论吹得天花乱坠,不如动手画图纸。我们来设计几个最核心的包装类。

1. NgxString:告别ngx_str_t的原始社会

原始的ngx_str_t就两个字段:datalen。我们要把它升级成“智能字符串”。

#ifndef NGX_STRING_HPP
#define NGX_STRING_HPP

#include <ngx_core.h>
#include <string>

class NgxString {
private:
    ngx_str_t m_str;

public:
    // 构造函数们:适应各种场景
    NgxString() { m_str.data = nullptr; m_str.len = 0; }
    NgxString(ngx_str_t & str) : m_str(str) {} // 引用现有内存
    NgxString(ngx_pool_t * pool, const std::string & s) {
        // 从std::string深拷贝,使用Nginx内存池
        m_str.data = (u_char *)ngx_palloc(pool, s.size());
        m_str.len = s.size();
        ngx_memcpy(m_str.data, s.c_str(), s.size());
    }

    // 空判断
    bool empty() const { return m_str.data == nullptr || m_str.len == 0; }
    // 获取长度
    size_t length() const { return m_str.len; }
    // 安全的获取C风格字符串(保证以'\0'结尾,需在内存池分配时预留空间)
    const char * c_str() const { return m_str.data ? reinterpret_cast<const char*>(m_str.data) : ""; }

    // 类型转换操作符,方便需要原版ngx_str_t的地方
    operator ngx_str_t() const { return m_str; }
    operator ngx_str_t*() { return &m_str; }

    // 比较操作符重载
    bool operator==(const NgxString & other) const {
        if (m_str.len != other.m_str.len) return false;
        return ngx_strncmp(m_str.data, other.m_str.data, m_str.len) == 0;
    }

    // 转换成std::string(方便使用C++算法)
    std::string toStdString() const {
        return empty() ? std::string() : std::string(c_str(), length());
    }
};

#endif // NGX_STRING_HPP

看,有了这个类,我们就能安全、直观地操作字符串了!

2. NgxRequest:核心请求对象的“全能秘书”

ngx_http_request_t *r是万恶之源,也是万能之源。我们来给它配个秘书。

#ifndef NGX_REQUEST_HPP
#define NGX_REQUEST_HPP

#include <ngx_http.h>
#include "NgxString.hpp"
#include <map>

class NgxRequest {
private:
    ngx_http_request_t *m_r;

public:
    NgxRequest(ngx_http_request_t * r) : m_r(r) {}

    // 获取方法 (GET, POST, ...)
    NgxString getMethod() const {
        return NgxString(m_r->method_name);
    }

    // 获取URI
    NgxString getUri() const {
        return NgxString(m_r->uri);
    }

    // 获取指定Header,比如 getHeader("Host")
    NgxString getHeader(const std::string & key) const {
        ngx_table_elt_t *header = m_r->headers_in.headers;
        if (header) {
            for (; header; header = header->next) {
                NgxString headerName(header->key);
                if (headerName.toStdString() == key) {
                    return NgxString(header->value);
                }
            }
        }
        return NgxString(); // 返回空对象
    }

    // 获取所有Header(返回一个map,方便查找)
    std::map<std::string, std::string> getAllHeaders() const {
        std::map<std::string, std::string> headers;
        ngx_table_elt_t *h = m_r->headers_in.headers;
        for (; h; h = h->next) {
            headers[NgxString(h->key).toStdString()] = NgxString(h->value).toStdString();
        }
        return headers;
    }

    // 获取查询参数 (简单实现,复杂情况需解析args)
    NgxString getArgs() const {
        return NgxString(m_r->args);
    }

    // 设置返回状态码
    void setStatus(ngx_uint_t status) const {
        m_r->headers_out.status = status;
    }

    // 设置返回的Content-Type
    void setContentType(const NgxString & type) const {
        m_r->headers_out.content_type = type; // 这里利用了我们的类型转换操作符
    }

    // 发送Header
    ngx_int_t sendHeader() const {
        return ngx_http_send_header(m_r);
    }

    // 发送Body
    ngx_int_t sendBody(const NgxString & data) const {
        ngx_buf_t *b = ngx_create_temp_buf(m_r->pool, data.length());
        if (b == nullptr) return NGX_ERROR;
        ngx_memcpy(b->pos, data.c_str(), data.length());
        b->last = b->pos + data.length();
        b->last_buf = 1; // 这是最后一个buffer

        ngx_chain_t out;
        out.buf = b;
        out.next = nullptr;

        return ngx_http_output_filter(m_r, &out);
    }
};

#endif // NGX_REQUEST_HPP

这个NgxRequest类,一下子把处理HTTP请求的复杂度,从“专家模式”拉到了“新手友好模式”。

第六章:实战!用包装类重构HelloWorld模块

现在,让我们用上面打造的“瑞士军刀”,重新实现那个HelloWorld模块,看看代码能变得多简洁!

// src/nd_hello_world_improved.cpp
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ctime>
#include <cstring>

// 引入我们的C++包装类
#include "NgxRequest.hpp"
#include "NgxString.hpp"

// 使用C++11的匿名函数和auto特性
static std::string get_current_time() {
    std::time_t now = std::time(nullptr);
    std::tm* tm_now = std::localtime(&now);
    char time_str[100];
    std::strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_now);
    return std::string(time_str);
}

// 核心处理函数 - 现在变得多简洁!
static ngx_int_t ngx_http_nd_hello_world_handler(ngx_http_request_t *r) {
    // 使用我们的C++包装类!
    NgxRequest req(r);
    
    // 只处理GET和HEAD方法
    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;
    }
    
    // 构造响应 - 现在代码多清晰!
    std::string response = "Hello, World! The current time is ";
    response += get_current_time();
    response += "\n";
    
    NgxString response_str(r->pool, response);
    
    // 设置响应
    req.setStatus(NGX_HTTP_OK);
    req.setContentType(NgxString(r->pool, "text/plain; charset=utf-8"));
    
    // 发送
    rc = req.sendHeader();
    if (rc != NGX_OK) {
        return rc;
    }
    
    return req.sendBody(response_str);
}

// ... 剩下的模块定义、配置结构等与之前类似 ...

看!用了我们的C++包装类后,代码可读性大幅提升,而且安全性更高,你再也不用直接面对那些原始的C结构体指针了!

第七章:更复杂的实战:开发一个Echo模块

来点更有挑战的:一个Echo模块,返回客户端发送给我们的所有信息(方法、URI、Header等),让客户端知道我们到底收到了什么。

// src/nd_echo_module.cpp
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <sstream>

#include "NgxRequest.hpp"
#include "NgxString.hpp"

static ngx_int_t ngx_http_nd_echo_handler(ngx_http_request_t *r) {
    NgxRequest req(r);
    
    // 只处理GET和HEAD
    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;
    }
    
    // 开始构建详细的响应信息
    std::stringstream response;
    response << "=== Nginx Echo Module ===\n\n";
    
    // 基础信息
    response << "Method: " << req.getMethod().toStdString() << "\n";
    response << "URI: " << req.getUri().toStdString() << "\n";
    
    NgxString args = req.getArgs();
    if (!args.empty()) {
        response << "Args: " << args.toStdString() << "\n";
    }
    
    // 头部信息
    response << "\n--- Headers ---\n";
    auto headers = req.getAllHeaders();
    for (const auto & header : headers) {
        response << header.first << ": " << header.second << "\n";
    }
    
    std::string response_str = response.str();
    NgxString response_ngx(r->pool, response_str);
    
    // 设置并发送响应
    req.setStatus(NGX_HTTP_OK);
    req.setContentType(NgxString(r->pool, "text/plain; charset=utf-8"));
    
    rc = req.sendHeader();
    if (rc != NGX_OK) {
        return rc;
    }
    
    return req.sendBody(response_ngx);
}

配置这个模块:

location /echo {
    nd_echo;
}

现在访问/echo,你会看到所有请求的详细信息,这对于调试API客户端非常有用!

第八章:C++开发Nginx模块的注意事项

8.1 内存管理:理解Nginx内存池

Nginx使用自己的内存池系统,这既是福音也是陷阱:

  • 自动释放:当请求结束时,整个请求内存池会被自动释放,你不需要手动释放从该内存池分配的内存
  • 分层结构:Nginx有多个层次的内存池(全局、server、location、请求)
  • 谨慎使用C++ new/delete:尽量不要混用Nginx内存池和C++原生内存管理
// 好的做法:使用Nginx内存池
void* memory = ngx_palloc(r->pool, size);

// 危险的做法:混用内存管理
void* memory = malloc(size); // 需要手动free,容易内存泄漏

8.2 异常安全

Nginx是C程序,不知道C++异常是什么。所以:

  • 绝对不要让异常传播到C代码中(即跨越extern "C"边界)
  • 在C++代码内部可以使用异常,但必须在进入C代码前捕获
static ngx_int_t ngx_http_my_handler(ngx_http_request_t *r) {
    try {
        // 你的C++代码,可以使用异常
        do_something_that_might_throw();
    } catch (const std::exception& e) {
        // 记录错误并返回适当的HTTP状态码
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "C++ exception: %s", e.what());
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    return NGX_OK;
}

8.3 性能考量

虽然C++包装类很便利,但要注意性能:

  • 避免过度封装:在热路径(比如每个请求都会执行的代码)中避免不必要的拷贝
  • 内联小函数:像NgxString::empty()这样的函数应该声明为内联
  • 注意字符串转换toStdString()这样的调用可能涉及内存分配和拷贝,谨慎使用

第九章:什么时候该用C++开发Nginx模块?

虽然C++很强大,但并不是所有场景都适合用C++开发Nginx模块。

9.1 适合的场景

  • 高性能计算需求:复杂的业务逻辑,需要大量CPU计算
  • 现有C++代码复用:已经有用C++实现的核心算法或业务逻辑
  • 深度集成需求:需要与底层系统深度交互,或与其他C++库紧密集成
  • 实时性要求极高:微秒级的响应要求,不能有任何额外的进程间通信开销

9.2 不适合的场景

  • 简单的业务逻辑:Lua或JavaScript脚本就能搞定的事情
  • 快速原型开发:需要快速迭代和测试的业务需求
  • 团队C++经验不足:团队成员主要熟悉Java/Python/PHP等高级语言

一般建议:对于复杂的、性能敏感的核心业务逻辑,使用C++模块;对于简单的、经常变化的业务逻辑,使用Lua脚本或反向代理到应用服务器。

结语:给Nginx装上大脑,让你的业务飞起来

通过这篇教程,你已经掌握了:

  • ✅ Nginx模块的基本架构和工作原理
  • ✅ 如何使用C++开发简单的Nginx模块
  • ✅ 如何用C++包装类让开发更安全、舒适
  • ✅ 如何编译、配置和测试你的自定义模块
  • ✅ 什么时候该用(不该用)C++开发Nginx模块

现在,是时候发挥你的创造力,给Nginx赋予独一无二的超能力了!

无论是实时数据处理、复杂业务逻辑,还是高性能API网关,Nginx+C++的组合都能让你的应用性能提升一个数量级。

记住,技术只是工具,真正的魔法在于你用这些工具解决什么问题。去吧,用你的代码,创造一些让人惊叹的东西!

让Nginx不再只是流量搬运工,而是成为你业务系统的智能核心!


注意:本文中的代码示例需要在支持C++11或更新标准的编译器中进行编译,并在修改Nginx编译系统以支持C++后使用。生产环境部署前请充分测试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

值引力

持续创作,多谢支持!

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

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

打赏作者

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

抵扣说明:

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

余额充值