nginx 利用 C 措辞开拓,C/C++ 构建工具浩瀚,如手写 Makefile, GNU Autoconf, cmake 等,一些项目乃至专门为自己开拓了构建工具,如 boost 库等。nginx 利用哪种构建工具呢?很遗憾,末了一种,自己开拓。nginx 利用 shell 脚本掩护了一套自动天生 Makefile 的构建脚本,类似简化定制版的 Autoconf 。构建脚本位于代码库 auto/ 目录下,C 源码则位于 src/ 目录下。
nginx 构建脚本同时也用来编译附加模块。
显然,在 nginx 模块中可以自由利用 nginx 主体代码供应的 API 。须要把稳的是, 构建时的 nginx 版本必须与运行时的 nginx 版本精确匹配 ,否则 nginx 将谢绝加载。这大概是 nginx 作者开拓环境
nginx 所需开拓环境非常大略,我利用 Ubuntu 18.04 ,利用下列命令即可安装所需最小依赖。

sudo apt-get updatesudo apt-get install build-essential libpcre3-dev zlib1g-dev -y
接下来确定目标 nginx 版本,可利用 nginx -v 查看 nginx 版本,如 Ubuntu 18.04 自带 nginx 版本为 1.14.0 。
$ nginx -vnginx version: nginx/1.14.0 (Ubuntu)
获取目标 nginx 版本源码,可从 github 拉取。利用 -b 指定拉取版本,--depth 1 表示仅拉取 1 个提交,不要提交历史,这样可以快速完成拉取。
git clone -b release-1.14.0 --depth 1 https://github.com/nginx/nginx.git
在 nginx 代码仓库目录下实行如下命令即可构建天生 nginx 可实行文件。
auto/configure && make
auto/configure 脚本检讨开拓环境和所需依赖,天生 Makefile 脚本,如果有报错按提示修复后重试即可。make 命令利用 Makefile 构建天生 nginx 可实行文件。默认在代码仓库目录下新建一个名为 objs/ 的目录作为构建目录,构建脚本自动天生的干系文件和终极编译天生的 nginx 可实行文件也在该目录下。
测试运行刚刚天生的 objs/nginx 可实行文件,结果如下。
$ objs/nginx -vnginx version: nginx/1.14.0
至此,最简 nginx 开拓环境准备就绪。
把稳: 此 nginx 版本仅用最小依赖和最简配置构建,仅供开拓测试动态模块时利用,不可替代生产环境的 nginx 版本。
源码配置与目录构造模块源码在独立的文件夹下掩护 (又称之为插件 addon)。模块源码目录下需供应一个名为 config 的 shell 配置脚本,供应模块信息。nginx 构建脚本将 ngx_addon_dir 变量设置为模块源码路径,并实行 config 脚本获取模块信息。
在 nginx 代码仓库阁下新建一个名为 nginx-hello-module 的模块文件夹,创建一个 config 脚本文件和一个 C 措辞源码文件 hello_module.c,即得到一个最大略的模块示例,目录构造如下。
nginx/ # nginx 代码仓库├── auto/ # nginx 构建脚本目录└── src/ # nginx 源码目录, 其他文件夹暂未列出。nginx-hello-module/ # 模块源码目录├── config # 模块配置脚本, shell 脚本└── hello_module.c # 模块源码文件
编写 config 配置脚本内容如下:
# vim: set ft=sh et:ngx_addon_name=ngx_http_hello_modulengx_module_type=HTTPngx_module_name="$ngx_addon_name"ngx_module_srcs="$ngx_addon_dir/hello_module.c". auto/module
插件名 ngx_addon_name 和模块名 ngx_module_name 设置为 ngx_http_hello_module 。模块类型 ngx_module_type 设置为 HTTP 。源码文件列表 ngx_module_srcs 设置为 $ngx_addon_dir/hello_module.c。把稳: 源码路径必须添加 $ngx_addon_dir/ 前缀,构建脚本才能精确找到源码文件。语句 . auto/module 调用 nginx 供应的模块配置脚本,这条语句固定添加到 config 文件末了。
模块代码开拓我们稍后再说,现在可以先建一个空源码文件 hello_module.c 。
在 nginx 代码仓库下实行如下命令,增加配置上述 nginx-hello-module 模块。
auto/configure --add-dynamic-module=../nginx-hello-module/
在 nginx 代码仓库下实行如下命令编译模块。
make modules
竟然编译成功了!
得到动态模块文件 objs/ngx_http_hello_module.so 。但此时模块还不可用 (考试测验加载此模块将报错),由于我们还没有写任何代码。
我们知道,一个 C 程序的入口是 main() 函数。而一个 nginx 动态模块的入口是一个 ngx_module_t 工具,其构造定义如下。
typedef struct ngx_module_s ngx_module_t;struct ngx_module_s { / 私有字段 ... ... / void ctx; ngx_command_t commands; ngx_uint_t type; 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); / 扩展备用字段 ... ... /};
撤除私有字段和扩展备用字段,用户干系的字段可分为 3 个部分:
模块类型 ngx_uint_t type 和模块类型特定的信息 void ctx 。模块类型必须与 config 脚本配置的类型同等,本例即为 HTTP ,源码中用 NGX_HTTP_MODULE 表示。模块供应的指令列表 ngx_command_t commands 。列表以 ngx_null_command 结尾,列表可以为空 (仅包含一个 ngx_null_command 结尾标记) 。别的为模块生命周期管理函数,可全部设置为 NULL 。HTTP 模块对应的模块信息 (void ctx 字段) 为 ngx_http_module_t 类型,可注册多少 HTTP 模块处理函数,可全部设置为 NULL 。
#define NGX_HTTP_MODULE 0x50545448 / "HTTP" /typedef struct { ngx_int_t (preconfiguration)(ngx_conf_t cf); ngx_int_t (postconfiguration)(ngx_conf_t cf); void (create_main_conf)(ngx_conf_t cf); char (init_main_conf)(ngx_conf_t cf, void conf); void (create_srv_conf)(ngx_conf_t cf); char (merge_srv_conf)(ngx_conf_t cf, void prev, void conf); void (create_loc_conf)(ngx_conf_t cf); char (merge_loc_conf)(ngx_conf_t cf, void prev, void conf);} ngx_http_module_t;
下面来编写 hello_module.c 源码,为大略起见,首先开拓一个空模块吧。
首先引入 nginx 头文件,声明模块入口 ngx_module_t 工具,变量名必须为 config 脚本中配置的模块名,本例中即为 ngx_http_hello_module 。
#include <ngx_config.h>#include <ngx_core.h>#include <ngx_http.h>extern ngx_module_t ngx_http_hello_module;
接下来设置 HTTP 模块信息 ngx_http_module_t ,干系处理函数全部设置为 NULL 。
static ngx_http_module_t ngx_http_hello_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_command_t[] 设置为一个空列表,仅包含 ngx_null_command 结尾标记。
static ngx_command_t ngx_http_hello_commands[] = { ngx_null_command};
末了,定义模块入口工具 ngx_module_t 。开头私有字段利用 NGX_MODULE_V1 表示,结尾扩展备用字段利用 NGX_MODULE_V1_PADDING 表示。设置上述定义的 HTTP 模块信息 ngx_http_hello_module_ctx 和指令列表 ngx_http_hello_commands ,生命周期管理函数全部设置为 NULL 。
ngx_module_t ngx_http_hello_module = { NGX_MODULE_V1, &ngx_http_hello_module_ctx, / module context / ngx_http_hello_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};
至此,一个空模块开拓完成。这可以作为开拓 HTTP 模块的初始模板,我们将在此根本上逐渐增加功能。
在 nginx 代码仓库目录下实行 make modules ,即可重新编译天生动态模块文件 objs/ngx_http_hello_module.so 。由于我们没有修正模块配置,没有添加或删除源码文件,以是不须要重新实行 auto/configure 配置脚本,直接实行 make modules 即可。
测试运行 nginx在 nginx 代码仓库目录下新建一个测试配置文件 objs/nginx.conf ,内容如下:
# vim: set ft=nginx et:daemon off; # default onpid objs/nginx.pid;error_log stderr notice;load_module objs/ngx_http_hello_module.so;events {}http { access_log objs/access.log; server { listen 8080 default_server; return 200 "test\n"; }}
daemon off; 设置 nginx 进程不要后台化,保持前台运行,按 Ctrl+C 即可退出 nginx 。error_log stderr notice; 缺点日志直接输出到终端,方便测试运行时查看缺点日志,设置日志级别为 notice 。load_module objs/ngx_http_hello_module.so; 加载我们开拓的动态模块 ngx_http_hello_module.so 。listen 8080 default_server; HTTP 做事器监听 8080 端口,这样利用普通用户即可运行测试。return 200 "test\n"; HTTP 要求直接返回 "test" 字符串。
在 nginx 代码仓库目录下利用如下命令测试运行 nginx 。
objs/nginx -p "$PWD" -c objs/nginx.conf
-p "$PWD" 设置 nginx prefix 为当前目录。配置文件路径和配置文件中利用的相对路径利用相对付 prefix 的路径。-c objs/nginx.conf 设置配置文件路径。
可看到 nginx 启动并打印日志,按 Ctrl+C 后 nginx 退出。此时我们的模块还是空模块,没有发挥任何浸染。
Nginx 配置指令 - 天下你好当我们学习一种新的开拓技能时,第一个程序常日是 "hello world": 打印一条 "hello world" 语句,向天下问声好。第一次打仗 nginx 开拓时,我们不得不花韶光做一些准备事情。现在,终于是时候伸开双臂,说一声 "天下你好" 了。
我最早学习利用的是 Apache HTTP 做事器,其至今仍旧是一款精良强大的开源软件。一些团队由于分外缘故原由开始考试测验新产品,俄罗斯程序员 Igor Sysoev 开拓的 nginx 很快因其稳定性和高性能而声名鹊起。
最初学习利用 nginx 的感想熏染是,nginx 的配置文件彷佛比 apache 要大略友好一些 (在我对两者都不熟习的情形下) 。nginx 的配置文件彷佛是一种脚本,以是 nginx 配置项被称作指令 (directive) 。没错,nginx 不但是一个 HTTP 做事器,还是一个被设计得大略小巧的脚本措辞阐明器,并支持开拓添加新的指令。nginx 指令常日用于配置,我们称之为配置指令,换一种唬人的说法,叫做声明式指令。
现在我们设计一个 hello 指令输出 "hello world" 语句。
创建配置存储构造体
HTTP 配置分为 http/server/location 3 层构造。我们设计 hello 指令仅在最顶层 http {} 主区块 (block) 下利用和生效。HTTP 模块默认无配置存储空间,可设置 ngx_http_module_t::create_main_conf 函数创建主区块配置构造体。
我们设计本模块仅包含一个字符串参数,即要输出的语句。nginx 字符串类型为 ngx_str_t ,编写创建主配置构造体的函数 hello_create_main_conf() 如下:
static voidhello_create_main_conf(ngx_conf_t cf){ ngx_str_t conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); if (conf == NULL) { return NULL; } return conf;}
从配置内存池 cf->pool 分配一个字符串 ngx_str_t, 分配构造体将初始化为 0, 对 ngx_str_t 即空字符串。如果函数返回 NULL 则表示分配失落败, nginx 将报错退出。
更新 ngx_http_module_t ngx_http_hello_module_ctx ,设置 create_main_conf 为 hello_create_main_conf() 函数。
static ngx_http_module_t ngx_http_hello_module_ctx = { NULL, / preconfiguration / NULL, / postconfiguration / hello_create_main_conf, / create main configuration / NULL, / init main configuration / NULL, / create server configuration / NULL, / merge server configuration / NULL, / create location configuration / NULL / merge location configuration /};
创建指令
一个指令用一个 ngx_command_t 类型的数据构造表示。
typedef struct ngx_command_s ngx_command_t;struct ngx_command_s { ngx_str_t name; ngx_uint_t type; char (set)(ngx_conf_t cf, ngx_command_t cmd, void conf); ngx_uint_t conf; ngx_uint_t offset; void post;};#define ngx_null_command { ngx_null_string, 0, NULL, 0, 0, NULL }
name 指定指令名,如 hello 。type 是一个稠浊构造,包含指令类型、指令利用位置、指令参数个数等多种特性信息。利用 NGX_HTTP_MAIN_CONF 表示指令可在 http 主配置利用,NGX_CONF_TAKE1 表示指令接管 1 个参数。set 为指令处理函数,即 nginx 配置设置函数。conf 指示保存配置构造体的位置。利用 NGX_HTTP_MAIN_CONF_OFFSET 表示指令配置在 http 主配置下存储生效。offset 指示指令配置字段的位置。常日一个模块的配置是一个构造体,而一个指令的配置是个中一个字段,set 函数通过 offset 访问字段,这样不须要知道构造体的类型 (构造),就可以读写配置字段。模块只有一个配置项时,设置为 0 即可。post 对特定处理函数可增加后置处理函数,或增加传入参数。常日不该用,设为 NULL 。
声明指令处理函数 hello() :
static char hello(ngx_conf_t cf, ngx_command_t cmd, void conf);
创建 hello 指令如下:
static ngx_command_t ngx_http_hello_commands[] = { { ngx_string("hello"), NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1, hello, NGX_HTTP_MAIN_CONF_OFFSET, 0, NULL }, ngx_null_command};
编写指令处理函数
指令实行处理:
nginx 根据指令 type 字段设置的特性自动校验指令位置,参数个数等信息,并将指令语句解析为字符串数组 (类似 shell 命令行) ,保存到 cf->args ,再调用指令处理函数。指令处理函数实行成功时返回 NGX_CONF_OK ,发生缺点时返回缺点。为了简化和统一指令处理, nginx 预定义了许多标准指令处理函数,如 ngx_conf_set_str_slot() 将一个字符串参数解析保存为一个 ngx_str_t 配置项。hello 指令可复用 ngx_conf_set_str_slot() 函数获取参数值,再添加额外逻辑打印 hello 语句。编写指令处理函数 hello() 如下:
static charhello(ngx_conf_t cf, ngx_command_t cmd, void conf){ ngx_str_t str = conf; char rv; rv = ngx_conf_set_str_slot(cf, cmd, str); if (rv != NGX_CONF_OK) { return rv; } ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "HELLO %V", str); return NGX_CONF_OK;}
ngx_log_error() 是一个宏,终极将调用 ngx_log_error_core() 函数。ngx_log_error() 第 3 个参数 err 表示系统缺点码,无对应缺点码时利用 0 。nginx 未利用 C 标准库的 snprintf() 字符串格式化函数,而是自己实现了 ngx_snprintf() 函数,并自定义了类似的格式化字符串,个中 %V 表示输出 ngx_str_t 指针指向的字符串。
至此,代码开拓完成。在 nginx 代码仓库目录下实行 make modules 重新编译天生动态模块文件。
在配置文件 objs/nginx.conf http 配置下添加如下配置:
hello Nginx;
在 nginx 代码仓库目录下实行如下命令,nginx 日志将输出 "HELLO Nginx" 语句,按 Ctrl-C 退出 nginx 。
objs/nginx -p "$PWD" -c objs/nginx.conf
HTTP 要求处理器
nginx 定义了多个 HTTP 要求处理阶段 (phase) ,如读取完 HTTP 要求头后即进入 NGX_HTTP_POST_READ_PHASE 阶段。可在 HTTP 要求处理的各个阶段添加处理器函数,类似于 Java Servlet 中的 HTTP 过滤器 (Filter) 。
HTTP 处理器函数署名 (函数类型) 如下:
typedef ngx_int_t (ngx_http_handler_pt)(ngx_http_request_t r);
参数 r 为 HTTP 要求构造体。返回值为 NGX_DECLINED 时,表示连续实行下一个处理器。发生缺点时,返回 HTTP 缺点码,如做事器缺点 500 NGX_HTTP_INTERNAL_SERVER_ERROR ,nginx 将立即返回要求。
编写 HTTP 要求处理器 hello_handler() 如下,对每个 HTTP 要求打印一次 hello 语句,同时打印解析后的要求 uri 。利用 ngx_http_get_module_main_conf() 从 HTTP 要求工具获取 ngx_http_hello_module 模块关联的配置数据。
static ngx_int_thello_handler(ngx_http_request_t r){ ngx_str_t str = ngx_http_get_module_main_conf(r, ngx_http_hello_module); ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, "HELLO %V, uri: %V", str, &r->uri); return NGX_DECLINED;}
为 HTTP 模块编写一个 postconfiguration 函数 hello_init() ,将 HTTP 处理器 hello_handler() 注册到 NGX_HTTP_POST_READ_PHASE 阶段。nginx 将在完成配置解析 (实行完配置指令) 后实行 HTTP 模块的 postconfiguration 函数,以完成模块初始化。
static ngx_int_thello_init(ngx_conf_t cf){ ngx_http_handler_pt h; ngx_http_core_main_conf_t cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_POST_READ_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } h = hello_handler; return NGX_OK;}
更新 ngx_http_module_t ngx_http_hello_module_ctx ,设置 postconfiguration 为 hello_init() 函数。
static ngx_http_module_t ngx_http_hello_module_ctx = { NULL, / preconfiguration / hello_init, / postconfiguration / hello_create_main_conf, / create main configuration / NULL, / init main configuration / NULL, / create server configuration / NULL, / merge server configuration / NULL, / create location configuration / NULL / merge location configuration /};
至此,开拓完成。在 nginx 代码仓库目录下实行 make modules 重新编译天生动态模块文件,然后实行如下命令启动 nginx 。
objs/nginx -p "$PWD" -c objs/nginx.conf
利用浏览器或 curl 命令访问 http://localhost:8080/ ,每访问一次将看到 nginx 打印一次 hello 语句,及当前要求 uri 。类似如下输出:
2020/05/16 22:46:26 [notice] 7279#0: 1 HELLO Nginx, uri: /, client: 127.0.0.1, server: , request: "GET / HTTP/1.1", host: "localhost:8080"2020/05/16 22:46:27 [notice] 7279#0: 1 HELLO Nginx, uri: /, client: 127.0.0.1, server: , request: "GET / HTTP/1.1", host: "localhost:8080"
热更新 (reload)
nginx 还支持热更新 (reload) ,这是一个很有用的高等特性。在一直止 nginx 的情形下将配置文件中的 hello 指令修正如下:
hello "阿泉";
在 nginx 代码仓库目录下实行如下 reload 命令:
objs/nginx -p "$PWD" -c objs/nginx.conf -s reload
reload 命令将看到如下输出:
2020/05/16 23:09:31 [notice] 9617#0: HELLO 阿泉2020/05/16 23:09:31 [notice] 9617#0: signal process started
原 nginx 进程将看到如下输出。nginx 将重新进行配置初始化,创建新 worker 进程,并优雅退出旧 worker 进程。
2020/05/16 23:09:31 [notice] 9384#0: signal 1 (SIGHUP) received from 9617, reconfiguring2020/05/16 23:09:31 [notice] 9384#0: reconfiguring2020/05/16 23:09:31 [notice] 9384#0: HELLO 阿泉# ... ...2020/05/16 23:09:31 [notice] 9384#0: start worker process 96232020/05/16 23:09:31 [notice] 9385#0: gracefully shutting down
再次访问 http://localhost:8080/ 时,可看到 nginx 日志打印的 hello 语句也随之变成了新配置的 hello 语句。
2020/05/16 23:09:49 [notice] 9623#0: 3 HELLO 阿泉, uri: /, client: 127.0.0.1, server: , request: "GET / HTTP/1.1", host: "localhost:8080"
热更新 (reload) 功能非常有用,但在生产利用时一定要非常小心以避免故障。实际利用中可能用的并不多。
教程到此结束,下面扯些题外话。
吐槽与闲聊Nginx 文档还算完善,代码还算优雅,阅读 Nginx 源码对提升开拓水平颇有裨益,但其过程实在是烧脑和痛楚 (对我而言) 。Nginx 源码险些攒齐了传统 C 措辞编程的所有缺点。比如利用整数定义缺点码和列举类型,利用了迪杰斯特拉 (Dijkstra) 师长西席不建议利用的 goto 语句,一个整数字段 (如 cmd->type) 整合了多种列举类型信息,许多地方利用了动态类型 void 等。这些用法不受工具 (静态) 检讨和约束 (原作者的脑中可能有一幅清晰的场景图表?),对不熟习的开拓者来说不仅难以理解,而且非常危险!
但其背后每每又是出于性能 (和某种简洁性) 的考虑,大概是利用 C 措辞的情形下所能做出的最大努力。换句话说,(很多时候) 这是 C 措辞的局限性,而不是 Nginx 的问题。缺点处理的精确解法该当是 Java 受检讨的非常,但 C 措辞短缺非常 (Exception) 等高等特性,合理利用 (无效业务值) 缺点码和 goto 语句是优雅且高效的最佳实践之一。
措辞之争
本段内容随意马虎引起不适,建议跳过。
有时候想,Nginx 为什么不该用更高等的开拓措辞 (比如 C++) 编写,或者至少可以复用 Apache 根本库 APR 吧 ?实在又何止 Apache 根本库, Apache HTTP 做事器该当有很多组件都可以复用。但如果这样的话,Nginx 又怎么能叫 Nginx 呢 ? 大概只能是一个分外版本的 Apache HTTP 做事器,影响力和竞争力都很难超越官方正版(就像许多 Nginx 修正版很难超越 Nginx 一样) 。不止是 3 方根本库,Nginx 连 C 措辞标准库都试图避免直策应用,比如自己开拓了 ngx_snprintf() (但 Nginx 也不是全都自己来,比如合理利用了 pcre, zlib, openssl 等 3 方库) 。很多 C 措辞项目实在都在利用自己分外定制版的 C 措辞 (又一个范例缺陷) 。这让我想起《黑客与画家》文集上提到的 迎难而生 的问题 (值得其余开贴谈论) ,如果一个问题太随意马虎,谁都可以复制 (抄袭),那么它的核心竞争力在哪里?
Nginx 及其模块开拓本身是有一定门槛的,乃至 Nginx 本身建议不要滥用模块开拓 (而只管即便用 nginx 配置或内置的 perl/njs 脚本) 。
有 nodejs 粉说用 nodejs 几条语句就可以写出一个高性能 HTTP 做事器,如果 nginx 这样写成,结果会若何 ?在大家都在喊着 nodejs/python/php/golang/kotlin 天下第一的时候,老态龙钟的 C 措辞荣获 TIOBE 编程措辞排行榜 2019 年度措辞,最近 (2020 年 5 月) 又重夺排行榜第一。我不是针对谁,我是说 javascript/php/golang 等都是垃圾措辞 (python 和 kotlin 还算能用?)。我也不推举 C 措辞,C 措辞显然有很多缺陷 (过于底层),如果能够加上一些 C++ 特性 (特殊是类和 RAII) 那肯定会好很多。但是 C++ 特性太多,切实其实是一团浆糊,以是许多团队和项目不得不精心掌握一些边界,设计一个定制版的 C++ 措辞 (与 C 措辞类似)。这导致 C++ 措辞分裂,是个不好的旗子暗记,也是这个缘故原由导致许多声称办理这些问题的新措辞不断涌现。
结论: 贴近系统和硬件编程,C/C++ 是不错的选择,高等措辞首选 Java ,其他一些快速粗糙 (quick and dirty) 的场景可适当选用其他措辞。但一定要小心避免垃圾措辞 (不再逐一点名了) 和所谓的领域专用措辞 (DSL) 。
代码风格
首选吐槽一下。Nginx 只利用 C 风格的注释 / / (不该用 C++ 的双斜杠 // 注释) 。利用 4 个空格缩进 (而不是 tab) 。变量名常常太短 (导致含义不直不雅观) 。单行源码不超过 80 个字符 (可能也是导致变量名过短的缘故原由)。这几点个人不太喜好。
听说 nginx 作者有代码洁癖,哀求字段名 (变量名) 排版对齐。我也有代码洁癖,我反对这种对齐,表面上视觉整洁了,实际上掩护跟踪很麻烦 (特殊是没有工具支持的情形下) 。再看 nginx 代码,不仅哀求对齐,而且是抛开润色符后的单词对齐 (嗯,奇怪的排版) 。如 struct ngx_command_s 定义如下。
struct ngx_command_s { ngx_str_t name; ngx_uint_t type; char (set)(ngx_conf_t cf, ngx_command_t cmd, void conf); ngx_uint_t conf; ngx_uint_t offset; void post;};
nginx 代码是很吝惜注释的,但并非没有注释,恰当的时候会有注释,而更多的时候让代码自己说话。如 ngx_http_core_generic_phase() 函数的这段代码,结合注释可知这里已经考虑列举了 rc 的所有可能取值。这点我是比较讴歌的,不过个人建议可以适当添加更多注释 (特殊是逻辑繁芜的地方) 。
if (rc == NGX_DECLINED) { r->phase_handler++; return NGX_AGAIN; } if (rc == NGX_AGAIN || rc == NGX_DONE) { return NGX_OK; } / rc == NGX_ERROR || rc == NGX_HTTP_... / ngx_http_finalize_request(r, rc); return NGX_OK;
其余,nginx 代码鼓励用空行分割语义块 (哪怕只有一行) ,如 ngx_conf_handler() 函数包含如下代码块:
/ set up the directive's configuration context / conf = NULL; if (cmd->type & NGX_DIRECT_CONF) { conf = ((void ) cf->ctx)[cf->cycle->modules[i]->index]; } else if (cmd->type & NGX_MAIN_CONF) { conf = &(((void ) cf->ctx)[cf->cycle->modules[i]->index]); } else if (cf->ctx) { confp = (void ) ((char ) cf->ctx + cmd->conf); if (confp) { conf = confp[cf->cycle->modules[i]->ctx_index]; } }
if 子句和 else 子句实行不同的逻辑,用一个空行分开,构造更加清晰,这一点值得学习。顺便说句,这段代码较难读懂,也容许以再适当添加部分注释。
末了,很多人可能听过类似 "单个函数不要超过 100 行" (更有严格的说 50 行, 20 行) 这样的最佳实践。但如果我们看许多精良开源项目的代码,大佬们写起代码来根本停不下来,洋洋洒洒几百行的核心函数纯属正常。只管即便保持函数功能单一和简短当然是最近实践,但是 不用去世守规则 。规则每每是由强者制订来约束弱者,黑客从来不应该受任何详细规则的束缚,唯一的规则便是精确、简短、健壮,然后越快越好。别给我说那些婆婆妈妈的编程规范。
我的代码又快又稳定,然后你跑来说我排版不好看 (是的我说了) ?滚一边去!
初次打仗一种开拓技能,彷佛来到一座花园,想要到某个目的地取采摘一朵花 (开拓需求)。陌生的花园犹如迷宫,一开始我们跌跌撞撞,可能被荆棘扎手,可能走错方向,但终极来到玫瑰花栏,摘下一朵花。于是我沿途做下暗号,小心避开荆棘和弯路,就成了这篇文章。
所有本文更适宜作为大略的快速参考 (沿路暗号),而读者可能会充满 “这里为什么要这样?” 的疑问。许多疑问都可以在 Nginx 官方 开拓指南 和 源码 里找到答案,那才是真正的藏宝图。只有我们亲自摸索熟习了这座花园,才会创造许许多多的宝藏,你大概会创造,阁下花栏有更俏丽的郁金喷鼻香和暗香的茉莉花。 先读代码,后浪。
本文为阿里云原创内容,未经许可不得转载。