网络资源

  1. http://emiller.info/nginx-modules-guide.html, 好像这是唯一的一份模块开发文档.. 以下简称 guide
  2. 读代码!!

nginx 程序启动 (配合 guide 的 'Components of an Nginx Module' 章节)

  1. 把 nginx.conf 读入内存
  2. 处理模块 ngx_http_module_t 定义的 configuration. 应该是如下的执行顺序
    • pre
    • create_main_conf
    • 执行 ngx_command_t 里面定义的函数. 可以视为 init_main_conf 的自动处理部分
    • init_main_conf
    • ...
    • postconfig
  3. listen
  4. fork 子进程(真正的服务器进程 master), 父进程退出

master 执行 setsid 脱离终端; getpid 写入 pid 文件; 启动 worker

Handlers, Filters

  1. Handler. Handler 可以做的工作包括:
    • 作为 handler-chain 的一部分, 返回 NGX_DECLINED 指示继续交给后继的 chain 运行.
      1. 现在 mod_passport 就仅仅是在这个机制上工作的
      2. handler-chain 分成好几个 phases, 包括 NGX_HTTP_POST_READ_PHASE, NGX_HTTP_SERVER_REWRITE_PHASE, ... 参考 src/http/ngx_http_core_module.h
    • 直接处理 request, 返回 response
  2. Filter. 貌似 handler 的 phases 缺乏处理返回给客户端的内容的阶段. 于是这个任务就在 Filter 里面完成了
    1. Header Filter
    2. Body Filter

Upstream Handler, Load-Balancer

基本上需要用到这个来写模块的可能性就很小了

  1. Upstream Handler. 从guide的翻译:"假设需要和后台服务器通信,比如 FastCGI, Memcached.. 那怎么才能避免网络 IO 阻塞 primary event loop 呢?这就要靠 Nginx 内置的 upstream 网络连接机制, 以及 hook 对应的 handler"
  2. Load-Balancer. 咱们邮件中心应用这个机制的一种可能是把请求 dispatch 到后端的桶上去... 这样恐怕 Upstream 需要能配置为泛域名——因为如果每增加一个桶前面就改一次配置就太恶心了——或者自己实现一个 Upstream 连接机制

这里多说一句,只要架构准备好了,扩展其他业务就简单。比如 nginx 的 smtp/pop3/imap4 反向代理功能

内存池, 数据结构, 以及对应函数

忍不住要抱怨一下, 相比较于 apr, nginx 的内置数据结构及其函数简直太XXXX了, 这直接导致了编写 module 成为一件门槛很高的工作

  1. 常用函数的包装(也是为了跨平台编译, 甚至更好的性能), 见 src/core/ngx_string.h
    • ngx_memset
    • ngx_memzero
    • ngx_memcpy
    • ...
  2. 资源池 pool, 见 src/core/ngx_palloc.c
    • ngx_palloc, 这个是最基本的malloc包装
    • ngx_calloc, 包装完 ngx_palloc 后再 memzero 一下
    • ngx_pool_cleanup_add, 正是因为这个功能的存在,pool 成为一个资源池而不仅仅是内存池. 调用 ngx_pool_cleanup_add 后返回一个指针 ptr, 然后设置 ptr->handler 为释放资源的函数就可以. 具体参考 ngx_destroy_pool 看看是怎么调用 handler 的
    • 神秘的 temp_pool, 还没有搞清楚为什么许多数据结构里面除了 pool 外还提供了一个 temp_pool
  3. ngx_str_t, 它提供了一个存储 binary string 的方案. 不过并没有提供 realloc 相关的运算(这样的话内存管理就复杂了)
    在 log/sprintf 函数族输出的时候, 用 %V 来对应 ngx_str_t *, 以避免哪些没有 '\0' 结尾的字符串输出
  4. ngx_array_t, 本质是一个栈表. 就我所观察, 它是所有复杂数据结构的基石, 好在不难理解.
    提示:ngx_array_push 的用法和 ngx_pool_cleanup_add 类似, 也是先返回一个指针, 然后调用者再对这个指针做操作——而不是调用者先在外面把 insert/push 的数据准备好再传递进去. 从此可一窥 nginx 的编程风格.
  5. ngx_list_t
    • 提供一个单向链表, 链表的每个单元是一个固定大小的 array
    • 这个东东比 ngx_array_t 有什么优势呢??array 每次都扩大内存都 double palloc 再 memcpy, ngx_list_t 效率上会好一些.
    • 仍然是 ngx_list_push() 这样的风格
    • 虽然效率高, 但 ngx_list_t 被使用的相比 ngx_array_t 就少多了, 大概和迭代算法写起来稍麻烦有关系.
  6. src/core/ngx_hash.c
    • ngx_table_elt_t 是在 ngx_hash.h 里面定义的,也算一个常用的数据结构,但和 hash table 没有什么关系
    • 首先是 ngx_hash_keys_arrays_t, 它几乎可以说是一个完备的 hash table
      1. 'keys' 成员就是当向里面添加 key/value 的时候, 依次 push 进去的 array
      2. 'keys_hash' 和 'hsize' 对应, 用来分布 hash, 在初始化的时候 palloc 好,以后就无法改变了.
      3. ngx_hash_keys_array_init() 里面的 asize 就是初始化 hash table 的 'keys' array 大小.
      4. ngx_hash_add_key() 用来放 key/value
      5. 这个数据结构只提供了 add 方法(甚至没有 set), 遍历之也还算简单, 就是没有快速的 get 的实现!!
      6. 虽然没有 get, 但 ngx_hash_add_key() 里的 ngx_hash_key_t *hk->key_hash 为接下来的操作埋下了伏笔
    • 看起来 ngx_hash_t 才是真正可以 get(find) 的 hash_table
      1. ngx_hash_t 好像不能一个一个的插入,只能从一个 array table 里面去初始化.
      2. 一个有趣的事实是,nginx 自己的模块里面,所有用 ngx_hash_keys_arrays_t 的地方,最后都把 'keys' 用ngx_hash_init() 初始化出一个 ngx_hash_t
    • (为什么 nginx 要这么设计 hash table 呢?难道和 wildcard 支持相关?没有继续深究了,现有的知识已经足够完成 mod_passport 的功能了)
  7. ngx_rbtree 红黑树
  8. ngx_radix_tree radix树用来放路由表这样的结构是比较好的... 看看代码, 果然是只在 geo_module 里面用到了这个结构
  9. ngx_regex

其它

guide的 Advanced Topics

  1. 介绍 worker 之间跨进程共享数据的, 涵盖了 src/core 目录下的 ngx_shmtx.c ngx_slab.c ngx_spinlock.c
  2. Subrequests