Openresty开发入门

openresty
OpenResty(又称:ngx_openresty) 是一个基于 NGINX 的可伸缩的 Web 平台,由中国人章亦春发起,提供了很多高质量的第三方模块。OpenResty 是一个强大的 Web 应用服务器,Web 开发人员可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,更主要的是在性能方面,OpenResty可以 快速构造出足以胜任 10K 以上并发连接响应的超高性能 Web 应用系统。

Openresty 是一个基于 Nginx 和 Lua 的高性能 Web 平台。其主要组成部分为:

  1. Nginx
  2. Lua 虚拟机
  3. lua-nginx-module: 将 Lua 虚拟机嵌入 Nginx 中,并提供Nginx API 供 Lua 调用的项目。在 Lua 层面可以通过使用这些 API 达到非阻塞的效果,这主要归功于 cosocket 和 nginx event 模型
  4. stream-lua-nginx-module: 与 lua-nginx-module 功能相似,区别是 lua-nginx-module 提供的是 nginx 的 http 模块的 API,而 stream-lua-nginx-module 提供的是 nginx 的 stream 模块的 API。
  5. lua-resty-core: 使用 FFI,提供了一系列 Lua 层面通用的 API
  6. lua-resty-*: 在以上几个模块的基础上,封装了一系列常用服务的模块。如: lua-resty-redis/lua-resty-mysql/lua-resty-http

Open Resty Http处理阶段

nginx把http请求处理流程划分为了11个阶段,这样划分的原因是将请求的执行逻辑细分,以模块为单位进行处理,各个阶段可以包含任意多个HTTP模块并以流水线的方式处理请求。这样做的好处是使处理过程更加灵活、降低耦合度。

1)NGX_HTTP_POST_READ_PHASE:

接收到完整的HTTP头部后处理的阶段,它位于uri重写之前,实际上很少有模块会注册在该阶段,默认的情况下,该阶段被跳过。

2)NGX_HTTP_SERVER_REWRITE_PHASE:

URI与location匹配前,修改URI的阶段,用于重定向,也就是该阶段执行处于server块内,location块外的重写指令,在读取请求头的过程中nginx会根据host及端口找到对应的虚拟主机配置。

3)NGX_HTTP_FIND_CONFIG_PHASE:

根据URI寻找匹配的location块配置项阶段,该阶段使用重写之后的uri来查找对应的location,值得注意的是该阶段可能会被执行多次,因为也可能有location级别的重写指令。

4)NGX_HTTP_REWRITE_PHASE:

上一阶段找到location块后再修改URI,location级别的uri重写阶段,该阶段执行location基本的重写指令,也可能会被执行多次。

5)NGX_HTTP_POST_REWRITE_PHASE:

防止重写URL后导致的死循环,location级别重写的后一阶段,用来检查上阶段是否有uri重写,并根据结果跳转到合适的阶段。

6)NGX_HTTP_PREACCESS_PHASE:

下一阶段之前的准备,访问权限控制的前一阶段,该阶段在权限控制阶段之前,一般也用于访问控制,比如限制访问频率,链接数等。

7)NGX_HTTP_ACCESS_PHASE:

让HTTP模块判断是否允许这个请求进入Nginx服务器,访问权限控制阶段,比如基于ip黑白名单的权限控制,基于用户名密码的权限控制等。

8)NGX_HTTP_POST_ACCESS_PHASE:

访问权限控制的后一阶段,该阶段根据权限控制阶段的执行结果进行相应处理,向用户发送拒绝服务的错误码,用来响应上一阶段的拒绝。

9)NGX_HTTP_TRY_FILES_PHASE:

为访问静态文件资源而设置,try_files指令的处理阶段,如果没有配置try_files指令,则该阶段被跳过。

10)NGX_HTTP_CONTENT_PHASE:

处理HTTP请求内容的阶段,大部分HTTP模块介入这个阶段,内容生成阶段,该阶段产生响应,并发送到客户端。

11)NGX_HTTP_LOG_PHASE:

处理完请求后的日志记录阶段,该阶段记录访问日志。

以上11个阶段中,HTTP无法介入的阶段有4个:

3)NGX_HTTP_FIND_CONFIG_PHASE

5)NGX_HTTP_POST_REWRITE_PHASE

8)NGX_HTTP_POST_ACCESS_PHASE

9)NGX_HTTP_TRY_FILES_PHASE

剩余的7个阶段,HTTP模块均能介入,每个阶段可介入模块的个数也是没有限制的,多个HTTP模块可同时介入同一阶段并作用于同一请求。

OpenResty 与之对应的阶段如下图所示:

Lua Nginx 指令

init_by_lua*:

在nginx重新加载配置文件时,运行里面lua脚本,常用于全局变量的申请。(例如:lua_shared_dict共享内存的申请,只有当nginx重起后,共享内存数据才清空,这常用于统计。)

init_worker_by_lua*:

开启 master 进程模式,Nginx 工作进程启动时执行指定的 Lua 代码。关闭 master 模式,将在 init_by_lua 后直接运行。这个指令经常被用来创建单进程的反复执行定时器(通过 ngx.timer.at Lua API 创建),可以是后端服务健康检查,也可以是其他定时的日常工作。

ssl_certificate_by_lua*:

当 Nginx 开始对下游进行 SSL(https) 握手连接时,执行该阶段,可以针对来自客户端的 SSL 握手请求做一些处理

set_by_lua*:

流程分支处理判断变量初始化(设置一个变量,常用与计算一个逻辑,然后返回结果,该阶段不能运行Output API、Control API、Subrequest API、Cosocket API)

该指令被设计为执行短小、快速的代码块,因为代码执行时Nginx的事件循环是被阻塞的(不支持非阻塞 I/O)。因此应避免耗时的代码处理。

set_by_lua的上下文中,至少下列 API 函数目前是被禁止的:

rewrite_by_lua*:

转发、重定向、缓存等功能 (例如特定请求代理到外网,在access阶段前运行,主要用于rewrite)

该阶段可以调用 全部 API,并作为一个新的协程,在一个独立的全局环境中执行(就像一个沙盒)。

access_by_lua*:

IP准入、接口权限等情况集中处理(例如配合iptable完成简单防火墙,主要用于访问控制,能收集到大部分变量,类似status需要在log阶段才有。这条指令运行于nginx access阶段的末尾,因此总是在 allow 和 deny 这样的指令之后运行,虽然它们同属 access 阶段。)

该阶段可以调用 全部 API,并作为一个新的协程,在一个独立的全局环境中执行(就像一个沙盒)。

content_by_lua*:

内容生成,阶段是所有请求处理阶段中最为重要的一个,运行在这个阶段的配置指令一般都肩负着生成内容(content)并输出HTTP响应。

该阶段可以调用 全部 API,并作为一个新的协程,在一个独立的全局环境中执行(就像一个沙盒)。

不要将本指令和其他内容处理程序指令放到同一个 location 中。 比如,本指令和 proxy_pass 指令就不能在同一个 location 中使用。

balancer_by_lua*:

该指令执行上游的负载均衡 Lua 代码(任何上游实体),代码配置在 upstream {} 小节中,该指令配置的 Lua 代码在单个下游请求中可能被调用多次,这里 Lua 代码的执行环境不支持 yield 操作,所以可能 yield 的 Lua API (例如 cosockets 和 “轻线程”),在这个环境中是被禁用的。一个可以使用并绕过这个限制的玩法,是可以在更早的阶段处理

header_filter_by_lua*:

应答HTTP过滤处理,一般只用于设置Cookie和Headers等,该阶段不能运行Output API、Control API、Subrequest API、Cosocket API(例如添加头部信息)。

body_filter_by_lua*:

应答BODY过滤处理(例如完成应答内容统一成大写)(一般会在一次请求中被调用多次, 因为这是实现基于 HTTP 1.1 chunked 编码的所谓“流式输出”的,该阶段不能运行Output API、Control API、Subrequest API、Cosocket API)

log_by_lua*:

会话完成后本地异步完成日志记录(日志可以记录在本地,还可以同步到其他机器)(该阶段总是运行在请求结束的时候,用于请求的后续操作,如在共享内存中进行统计数据,如果要高精确的数据统计,应该使用body_filter_by_lua,该阶段不能运行Output API、Control API、Subrequest API、Cosocket API)

它不替代当前access的日志,而是在其前面执行。

注意,当前环境中以下 API 函数当前是被禁用的:

Nginx Conf 配置示例

# 设置纯 Lua 扩展库的搜寻路径(';;' 是默认路径):
lua_package_path '/foo/bar/?.lua;/blah/?.lua;;';

# 设置 C 编写的 Lua 扩展模块的搜寻路径(也可以用 ';;'):
lua_package_cpath '/bar/baz/?.so;/blah/blah/?.so;;';

# 指定的 Lua 代码,以及 Lua 模块的 Lua 代码缓存。
lua_code_cache off;

server {
location /lua_content {
# 通过 default_type 设置默认的 MIME 类型:
default_type 'text/plain';

content_by_lua_block {
ngx.say('Hello,world!')
}
}

location /nginx_var {
# 通过 default_type 设置默认的 MIME 类型:
default_type 'text/plain';

# 试试访问 /nginx_var?a=hello,world
content_by_lua_block {
ngx.say(ngx.var.arg_a)
}
}

location /request_body {
client_max_body_size 50k;
client_body_buffer_size 50k;

content_by_lua_block {
ngx.req.read_body() -- explicitly read the req body
local data = ngx.req.get_body_data()
if data then
ngx.say("body data:")
ngx.print(data)
return
end

-- body may get buffered in a temp file:
local file = ngx.req.get_body_file()
if file then
ngx.say("body is in file ", file)
else
ngx.say("no body found")
end
}
}

# 在子请求中直接发起 Lua 非阻塞 I/O 调用
# (其实,更好的方式是使用 cosockets)
location /lua {
# 通过 default_type 设置默认的 MIME 类型:
default_type 'text/plain';

content_by_lua_block {
local res = ngx.location.capture("/some_other_location")
if res then
ngx.say("status: ", res.status)
ngx.say("body:")
ngx.print(res.body)
end
}
}

location = /foo {
rewrite_by_lua_block {
res = ngx.location.capture("/memc",
{ args = { cmd = "incr", key = ngx.var.uri } }
)
}

proxy_pass http://blah.blah.com;
}

location = /mixed {
rewrite_by_lua_file /path/to/rewrite.lua;
access_by_lua_file /path/to/access.lua;
content_by_lua_file /path/to/content.lua;
}

# 在代码中使用 Nginx 变量
# 注意: Nginx 变量的内容一定要做仔细的过滤,否则会有很大的安全风险
location ~ ^/app/([-_a-zA-Z0-9/]+) {
set $path $1;
content_by_lua_file /path/to/lua/app/root/$path.lua;
}

location / {
lua_need_request_body on;

client_max_body_size 100k;
client_body_buffer_size 100k;

access_by_lua_block {
-- 检测客户端 IP 地址是否在我们的黑名单中
if ngx.var.remote_addr == "132.5.72.3" then
ngx.exit(ngx.HTTP_FORBIDDEN)
end

-- 检测客户端 URI 数据是否包含禁用词汇
if ngx.var.uri and
string.match(ngx.var.request_body, "evil")
then
return ngx.redirect("/terms_of_use.html")
end

-- tests passed
}

# proxy_pass/fastcgi_pass/etc settings
}
}

参考:

0%