Lua 语法知识点记录

table {:toc} Lua 是一门小巧的编程语言,但麻雀虽小五脏俱全,而且与 C 语言的交互非常友好,所以有人称它是 “胶水语言”。最近在研究 nginx,另一个广泛应用的、基于 nginx 的开源项目 OpenResty 就是把 lua 嵌入到了 nginx 中,很有意思。于是就来学习一下 lua。 基本语法 单行注释用 --,多行注释用 --[[ 多行注释 --]] 数据类型 nil 表示一个无效值(在条件表达式中相当于false)。 boolean 包含两个值:false和true。 number 表示双精度类型的实浮点数 string 字符串由一对双引号或单引号来表示 function 由 C 或 Lua 编写的函数 userdata 表示任意存储在变量中的C数据结构 thread 执行协同程序 table 表或者数组 table 类型 在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 local tbl1 = {} local tbl2 = {"apple", "pear", "orange", "grape"} 另外,数组的索引可以是数字或者是字符串。比如有以下代码: a = {} a["key"] = "value" key = 10 a[key] = 22 a[key] = a[key] + 11 最后 table a 中的内容是 (10, 33) 和 (key, value) ...

2018-12-09 · Me

nginx HTTP 的 11 个阶段

nginx 源码的特点是用了很多回调函数,阅读起来非常麻烦,因为不知道当前这个 hanlder 到底对应哪个函数。 在正式开始研究这 11 个阶段之前,我们先看几个结构体,然后再看 ngx_http_core_run_phases() 函数,希望能更快的理解这些 phase 是怎么 run 的。 ngx_http_core_main_conf_t 回顾一下 ngx_http_core_main_conf_t,在前面的博客中已经介绍过,它还有两个兄弟 ngx_http_core_srv_conf_t 和 ngx_http_core_loc_conf_t。 ngx_http_core_main_conf_t 中有两个成员是本文比较关心的: phase_engine 和 phases。 typedef struct { ngx_array_t handlers; } ngx_http_phase_t; typedef struct { // 所有的http请求都要使用这个引擎处理 ngx_http_phase_engine_t phase_engine; // http handler模块需要向这个数组添加元素 ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE + 1]; } ngx_http_core_main_conf_t; 配置解析后的 postconfiguration 里向cmcf->phases数组添加元素,phases数组存放了所有的phase,其中每个元素是ngx_http_phase_t类型的,表示的就是对应的phase handler的数组。ngx_http_core_main_conf_t->phases数组主要用于handler的注册。 ngx_http_phase_engine_t typedef struct { ngx_http_phase_handler_t *handlers; ngx_uint_t server_rewrite_index; ngx_uint_t location_rewrite_index; } ngx_http_phase_engine_t; ngx_http_phase_handler_t struct ngx_http_phase_handler_s { ngx_http_phase_handler_pt checker; ngx_http_handler_pt handler; ngx_uint_t next; }; 看完了相关数据结构,特别是看到 checker、handler 的时候,是不是突然觉得熟悉了?没错,这就是上一篇博客 http 请求处理流程中,最后的 run core phase。 ngx_http_core_run_phases(ngx_http_request_t *r){ ngx_http_phase_handler_t *ph; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); ph = cmcf->phase_engine.handlers; while (ph[r->phase_handler].checker) { rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]); } } phase_engine[] 中的成员 看了上面的代码不禁有疑问:phase_engine[] 的成员是在哪赋值的呢?答案是 ngx_http_init_phase_handlers() ...

2018-12-09 · Me

nginx HTTP 链接建立过程

ngx_http_init_connection() 负责建立 http 链接的是 ngx_http_init_connection(), 我们先来看一下谁会调用这个函数。 ngx_http_optimize_servers() 是一个负责合并配置项的函数,在有关 http 配置项解析的博客中曾经提到过,它会调用 ngx_http_add_listening()。 继续看 ngx_http_add_listening(), 看到 ls->handler = ngx_http_init_connection; 即 ngx_http_init_connection() 作为一个回调函数,被设置在了 listening 结构体的 handler 中。 那么它是从哪儿被调用的呢? 最简单的方法就是打印一下backtrace #0 ngx_http_init_connection (c=0x7ffff7fa90f8) #1 ngx_event_accept (ev=0x73c3a0) #2 ngx_epoll_process_events (cycle, timer, flags) #3 ngx_process_events_and_timers (cycle=cycle@entry=0x716860) #4 ngx_single_process_cycle (cycle=cycle@entry=0x716860) #5 main (argc=<optimized out>, argv=<optimized out>) 原来是在 epoll 中由事件触发的,正所谓事件驱动。现在知道了它的调用者,那么继续看 init connection 具体做了哪些事。 我们先忽略对 ipv6、ssl 的处理,直接看最简单的情况,ssl 相关后续博客再分析。精简后的代码如下: void ngx_http_init_connection() { c->data = hc; // data 是专门存数据的地方,之后使用 // 拿到以前提到很多次的 ngx_http_conf_ctx_t hc->conf_ctx = hc->addr_conf->default_server->ctx; // 连接的读事件,rev 是 ngx_event_t 类型 rev = c->read; // 处理读事件,读取请求头 // 设置了读事件的 handler,可读时就会调用 handler rev->handler = ngx_http_wait_request_handler; // 前面把读事件加入epoll,当socket有数据可读时就调用 // ngx_http_wait_request_handler // 同时因为事件也加入了定时器,超时时也会调用 handler ngx_handle_read_event(rev, 0); } ngx_http_wait_request_handler() 前面我们把 read 事件加入了 epoll,当 socket 有数据可读是就会调用本函数。又因为是事件触发,可能会被多次调用,即重入。 ...

2018-12-04 · Me

nginx 启动流程之 ngx_init_cycle()

从 main() 函数开始之后,很快就调用到 ngx_init_cycle(),这是 nginx 源码中一个非常重要的函数,它负责调用所有模块的init_module函数指针,初始化模块,并且解析 nginx.conf 文件中的各种参数。所以在分析 nginx 启动流程的时候,必须搞清楚这个函数做了哪些工作。 首先函数的传入参数只有一个,ngx_cycle_t *old_cycle。 当 main() 函数调用 ngx_init_cycle() 时,因为是第一次启动 nginx,给的参数是一个刚刚初始化的变量,只填写了一些必要的信息; 另一个会调用ngx_init_cycle()是 ngx_master_process_cycle()。因为 nginx 支持动态加载 nginx.conf 文件,所以此时的传入参数就是当前的配置。 分析 ngx_master_process_cycle() 就会了解 nginx master 进程是如何等待、处理信号,并且启动新的 worker 进程的。 先来看看 ngx_cycle_t 结构体里的成员,这里只列出本文关心的几个: struct ngx_cycle_s { // 存储所有模块的配置结构体,是个二维数组 // 0 = ngx_core_module // 1 = ngx_errlog_module // 3 = ngx_event_module // 4 = ngx_event_core_module // 5 = ngx_epoll_module // 7 = ngx_http_module // 8 = ngx_http_core_module void ****conf_ctx; // 保存模块数组,可以加载动态模块 // 可以容纳所有的模块,大小是ngx_max_module + 1 // ngx_cycle_modules()初始化 ngx_module_t **modules; // 拷贝模块序号计数器到本cycle // ngx_cycle_modules()初始化 ngx_uint_t modules_n; // 标志位,cycle已经完成模块的初始化,不能再添加模块 // 在ngx_load_module里检查,不允许加载动态模块 ngx_uint_t modules_used; } 其中最最重要的莫过于 conf_ctx 了,回顾上一篇博客里的图,重点关注一下其中 ngx_http_module,需要搞清楚这些配置是怎么解析到 conf_ctx 中去的。 ...

2018-12-02 · Me

nginx HTTP 配置项的解析

在前面的两篇博客中我们看到,无论是实现一个 http 模块,或者是 http filter 模块,都需要实现模块自己的 ngx_http_module_t 结构体。 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; 其中 main、srv、loc 分别对应 nginx.conf 中的 http,server,location 配置块,本文就来关注一下这些配置项是如何被解析和使用的。 解析 http 不同级别配置项 一个简单的 nginx.conf 配置如下: http { test_cmd; server { listen 80; test_cmd; location / { test_cmd; root html; index index.html index.htm; } } } 可以看到,在 http,server,location 三个配置块中都有我们自定义模块的命令 test_cmd。 ...

2018-11-25 · Me

nginx HTTP Filter 模块

过滤模块基本概念 普通的 HTTP 模块和 HTTP filter 模块有很大的不同。普通模块,例如上篇博客提到的 hello world 模块,可以介入 nginx http 框架的 7 个处理阶段,绝大多数情况下介入 NGX_HTTP_CONTENT_PHASE 阶段,特点是一旦介入了,那么一个 http 请求在这个阶段将只有这个模块处理。 http filter 模块则不同,一个请求可以被任意个 http 过滤模块处理,而且过滤模块 仅处理服务器发出的 HTTP 响应 header 和 body,不处理客户端发来的请求。 过滤链表的顺序 编译 nginx 的第一步是执行 configure 脚本生成 objs/ngx_modules.c 文件,这个文件中的 ngx_modules 数组会保存所有的 nginx 模块,包括普通的 http 模块和本文介绍的 http filter 模块。 nginx 在启动时初始化模块的顺序就是 nginx_modules 数组成员的顺序。因此,只要看 configure 命令生成的 ngx_modules.c 文件就可以知道所有 http 过滤模块的顺序。 对于 http 过滤模块来说,在 ngx_modules 数组中的位置越靠后,实际执行请求时就越先执行。因为在初始化 http 过滤模块时,每个过滤模块都是将自己插入到整个链表的首部。 开发一个 HTTP 过滤模块 一个简单的过滤模块实现这样的功能:在返回的 http response 中,先检查 header,如果是 200 OK,则在 body 中插入一段字符。如下所示: ...

2018-11-19 · Me

nginx 模块开发入门

编写模块 想要学习如何开发一个 nginx 模块,最快速简单的方法莫过于写一个 Hello World 模块,没错,还真有这么一个 nginx-hello-world-module,而且 nginx.org 官网还介绍了这个模块。 首先,对于所有的 nginx 模块来说,都需要实现一个 ngx_module_t 结构体,如下所示,需要特别注意的是,如果去看 module 结构体的定义,它与下面的代码并不是一一对应的,这是因为 NGX_MODULE_V1 宏把其他变量都赋值了,帮我们屏蔽了一些细节。 总而言之,开发一个 nginx 模块,我们跟着这个套路走就行了。 ngx_module_t ngx_http_hello_world_module = { NGX_MODULE_V1, &ngx_http_hello_world_module_ctx, /* module context */ ngx_http_hello_world_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 模块,那么 module type 那儿写上 HTTP 的宏就行了,都是固定死的操作。 ...

2018-11-18 · Me

nginx 文件锁、自旋锁的实现

在上一篇博客 Linux 共享内存以及 nginx 中的实现的示例中,我们看到每次多个进程同时对共享内存中的 count 加一,导致每次运行结果都不一样,那么解决的方法就是对临界区加锁了,所以本文就来研究一下 nginx 中的几种加锁方式。 文件锁 文件锁的原理就是在磁盘上创建一个文件(操作系统创建一个文件描述符),然后多个进程竞争去获取这个文件的访问权限,因此同一时刻只有一个进程能够访问临界区。 可以看出,进程并不会真正在这个文件中写什么东西,我们只是想要一个文件描述符 FD 而已,因此 nginx 会在创建了文件后把这个文件删除,只留下文件描述符。 多个进程打开同一个文件,各个进程看到的文件描述 FD 值可能会不一样。例如文件 test.txt 在 进程1 中是 101, 而在进程2中是 201 使用文件锁举例 使用文件锁主要用到两个 libc 提供的结构体和函数。 struct flock; 提供一些锁的基本信息,比如读锁 F_RDLCK, 还是写锁 F_WRLCK fcntl(): 对文件描述符进行操作的库函数。 那么如何用这个函数来实现锁的功能呢? 先看一个加锁的代码: void mtx_file_lock(struct fdmutex *m) { struct flock fl; memset(&fl, 0, sizeof(struct flock)); fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; if (fcntl(m->fd, F_SETLKW, &fl) == -1) { printf("[-] PID %d, lock failed (%s).\n", getpid(), strerror(errno)); } } 再来看一个解锁的代码: void mtx_file_unlock(struct fdmutex *m) { struct flock fl; memset(&fl, 0, sizeof(struct flock)); fl.l_type = F_UNLCK; fl.l_whence = SEEK_SET; if (fcntl(m->fd, F_SETLK, &fl) == -1) { printf("[-] PID %d, unlock failed (%s).\n", getpid(), strerror(errno)); } } 遇到临界区,只要在需要进行保护的代码前后加上上面的 lock() 和 unlock() 函数就可以了。 ...

2018-09-18 · Me