OpenResty完美地结合了Nginx的事件驱动机制和Lua的协程机制,所有的函数都是同步非阻塞的,处理请求时不需要像其他语言那样编写难以理解的异步回调函数,自然而且高效。
◆ 配置:
以下八个指令可以用在配置文件的http{}里调整OpenResty处理HTTP 请求时的行为。
1、lua_use_default_type on | off
在发送响应数据时是否在响应头宇段“ Content-Type ”里使用默认的MIME 类型,通常应设置为on 。
2、lua_malloc_trim num
设置清理内存的周期,参数num是请求的次数。当处理了num个请求后OpeηRe sty就会调用libc 函数mall oc trim ,把进程内的空闲内存归还给系统,从而最小化内存占用。
参数num 的默认值是1000 ,每1000个请求就会执行一次内存清理,可以根据实际情况设定num 。如果系统的内存足够大,或者不关心OpenResty的内存占用,那么可以设置为0,这将禁用内存清理。
3、lua_need_request_body on | off
是否要求OpenResty在开始处理流程前强制读取请求体数据,默认值是off ,即不会主动读取请求体。不建议把指令置为on状态,这将会增加无谓的读取动作,降低OpenResty的运行效率,可以使用ngx.req.read_body函数按需读取数据,更加灵活。
4、lua_http10_buffering on l off
启用或禁用HTTP1.0 里的缓冲机制,默认值是on。这个指令仅是为了兼容HTTP 1.0/0.9 协议,由于目前HTTP 1 .1 基本己经全面普及,建议把它置为off,可以加快Openresty的处理速度。
5、rewrite_by_lua_no_postpone on l off
是否让“ rewrite_by_lua ”在rewrite阶段的最后执行,默认值是off, 即“rewrite_by_lua ”里的Lua 代码将在其他Nginx rewrite 功能模块之后执行。除非有特殊需要或者对OpenResty的执行阶段有透彻理解,建议使用默认值off。
6、access_by_lua_no_postpone on l off
是否让access_by_lua在access阶段的最后执行,默认值是off , 即access_by_lua里的Lua代码将在其他Nginx access 功能模块之后执行。除非有特殊需要,建议使用默认值off 。
7、lua_transform_underscores_in_response_headers on l off
是否把Lua代码里响应头名字的“_”转换成“-”,默认值是on ,建议保持默认值。
8、lua_check_client_abort on l off
是否启用OpenResty的客户端意外断连检测,默认值是off。如果打开此指令, 则需要在Lua 程序里编写一个handler 来处理断连。
◆ 常量:
1、状态码:
状态码表示HTTP请求的处理状态,目前RFC规范里有一百多个, 在OpenResty 里只定义了少量最常见的,
ngx.HTTP_OK:200,请求己成功处理
ngx.HTTP_MOVED_TEMPORARILY:302,重定向跳转
ngx.HTTP_ BAD_REQUEST:400,客户端请求错误
ngx.HTTP_UNAUTHORIZED:401,未认证
ngx.HTTP_FORBIDDEN:403,禁止访问
ngx.HTTP_NOT_FOUND:404,资源未找到
ngx.HTTP_INTERNAL_SERVER_ERROR:500,服务器内部错误
ηgx. HTTP_BAD_GATEWAY:502,网关错误,反向代理后端无效响应
ngx.HTTP_SERVICE_UNAVAILABLE:503,服务器暂不可用
ngx.HTTP_GATEWAY_TIMEOUT:504,网关超时,反向代理时后端超时
在编写代码时不使用这些常量,直接用200、404这样的数字也是可以的,两者完全等价。
2、请求方法:
HTTP协议里有GET/POST/PUT 等方法,相应地Openresty也定义了这些常量
ngx.HTTP_GET:读操作,获取数据
ngx.HTTP_HEAD:读操作,获取元数据
ngx.HTTP_POST:写操作,提交数据
ngx.HTTP_PUT:写操作,更新数据
ngx.HTTP_DELETE:写操作,删除数据
ngx.HTTP_PATCH:写操作,局部更新数据
要注意的是这些常量并不是字符串,而是数字,主要用于ngx.req.set_method 和ngx.location.capture。
◆ 变量:
OpenResty使用表ngx.var操作Nginx变量,里面包含了所有的内置变量和自定义变量,可以用名字直接访问。
1、读变量:
ngx.var读取Nginx变量使用“.”或“[]”
ngx.var.uri -- 变量$uri,请求的URI
ngx.var['http_post'] -- 变量$http_host ,请求头里的ho st
if #ngx.var.is_argus > 0 then -- 检查是否有URI参数,$is_args
ngx.say(ngx.var.args) -- 输出URI里的参数, $args
end
ngx.var.xxx 虽然很方便,不过每次调用都会有一点点的额外开销(内部分配少量内存),所以不建议过度使用,应当尽量使用OpenResty 里等价的功能接口,如果必须要使用则最好local化暂存,避免多次调用
local uri = ngx.var.uri -- -- local化,避免多次调用ngx.var
2、写变量:
Nginx 内置的变量绝大多数是只读的,只有$args 、$limit_rate等极少数可以直接修改,强行修改只读变量会导致程序运行错误:
ngx.var.limit_rate = 1024*2 -- 改写限速变量为2K
ngx.var.uri =”unchangeable " -- 不可修改,会在运行日志里记录错误信息
配置文件里使用set指令自定义的变量是可写的,允许任意赋值修改,由于变量在处理流程中全局可见,所以我们可以利用它来在请求处理的各个阶段之间传递数,作用类似ngx.ctx。
set $new_log_var = 'a'
ngx.var.new_log_var =”log it" -- 修改变量,可在之后的log等阶段里使用
set_by_ lua是另一种改写变量的方式,它类似指令set或map,但能够使用Lua代码编写复杂的逻辑赋值给变量
set_by_lua_block $var { -- 赋值给变量$var
local len = ngx.var.request_length -- 获取请求长度♀ request_le ngth
return tonumber(len)*2 -- 加倍后赋值
}
不建议使用“set_by_lua,它的限制较多(阻塞操作,一次只能赋值一个变量,不能使用cosocket 等),使用“ set 指令+ ngx. var 接口”的方式更加灵活。
◆ 请求基本信息:
1、请求来源:
函数ngx.req.is_internal用来判断本次请求是否是由“外部”发起的
is_ internal = ngx.req.is_internal()
如果本次请求不是由服务器以外的客户端发起,而是内部的流程跳转或者子请求,那么函数的返回值就是true。
2、起始时间:
函数ngx.req.start_time可以获取服务器开始处理本次请求时的时间戳,精确到毫
秒,使用它可以随时计算出请求的处理时间,相当于$request_time但更廉价。
local request_time = ngx.now() - ngx.req.start_time()
3、请求头:
函数ngx.req.raw_header可以获得HTTP请求头的原始文本
local h = ngx.req.raw_header() -- 获取请求头原始字符串
利用正则表达式等工具可以解析字符串,提取出各种信息,不过推荐使用OpenResty提供的专用功能接口。
4、暂存数据:
OpenResty把请求处理划分成rewrite,access,content等若干个阶段, 每个阶段执行的都是彼此独立的程序,由于作用域的原因内部变量不能共用,如果想要在各个阶段间传递数据就需要使用ngx.ctx ,它比仅能存储字符串的ngx.var.xxx更灵活。
OpenResty 为每个HTTP请求都提供一个单独的表ηgx.ctx ,在整个处理流程中共享,可以用来保存任意的数据或计算的中间结果。
rewrite_by_lua_block { -- rewrite 阶段
local len = ngx.var.content length -- 使用变量获取文本长度
ngx.ctx.len = tonumber(len) -- 转换为数字,存入ctx表
}
access_by_lua_block { -- access 阶段
assert(not len) -- rewrite阶段的len变量不可用
assert(ngx.ctx.len) -- ngx.ctx里的len变量可以共享使用
}
content_by_lua_block { -- content阶段
ngx.say(ngx.ctx.len) -- ngx.ctx里的变量在其他阶段仍然可用
}
注:ngx.ctx的成本较高,应当尽量少用,只存放少量必要的数据,避免滥用。
◆ 请求行:
HTTP请求行里的信息包括请求方法、URI 、HTTP版本等,可以用 ngx.var获取
$request:完整的请求行(包含请求方法、URI 、版本号等)
$scheme:协议的名字,如“http”或“https”
$request_method:请求的方法
$request_uri:请求的完整URI(即地址+参数)
$uri:请求的地址,不含"?"及后面的参数
$document_uri:同$uri
$args:URI里的参数,即"?"后的字符串
$arg_xxx:URI里名为“xxx”的参数值
因为ngx.var的方式效率不高,而且是只读的,所以OpenResty在表ngx.req里提供了数个专门操作请求行的函数。这些函数多用在“ rewrite_by_lua ”阶段,改写URI 的各种参数,实现重定向跳转。
1、请求版本:
函数ngx.req.http_version以数字形式返回请求行里的HTTP协议版本号,相当于$server_protocol,目前的可能值是0.9 、1.0 、1.1 和 2。
2、请求方法:
函数ngx.req.get_method和ngx.req.set _method相当于变量$request_method,可以读写当前的请求方法。但两者的接口不太对称,前者的返回值是字符串,而后者的参数却不能用字符串,只能使用数字常量
ngx.req.get_method() -- 请求方法的名字,字符串
ngx.req.set_method(ngx.HTTP_POST) -- 改写请求方法,必须使用命名常量,可以自定义字符串替代:POST=ngx.HTTP_POST。
3、请求地址:
函数ngx.req.set_uri用于改写请求行里的“地址”部分($uri)
ngx.req.set_uri(uri, jump) -- 改写请求行里的URI
参数jump的默认值是false,仅修改URI而没有跳转动作。只能在rewrite_by_lua阶段里把它设置为true,这将执行一个内部重定向,跳转到本server内其他location 里继续处理请求(类似ngx.redirect ),在其他阶段置为true则会导致错误。
ngx.req.set_uri("/new_req_uri") -- 改写当前的URI,但不会跳转
URI中有时会出现“=”“%”“#”“&”等特殊字符,可以调用ngx.escape_uri或ngx.unescape_uri编码或解码
local uri ="a + b = c #!" -- 一个待编码的字符串
local enc= ngx.escape_uri(uri) -- 转义其中的特殊字符,结果为a%20%2B%20b%20%3D%20c%20%23!
local dec= ngx.unescape_uri(enc) -- 还原字符串,结果为a + b = c #!
4、请求参数:
(1)获取URI 参数:
函数ngx.req.get_uri_args用来获取URI 里的参数
args = ngx.req.get_uri_args(max_args) -- 获取URI 里的参数
它从请求行里获取URI参数字符串,以key-value表的形式返回解析后的结果。参数max_args指示函数解析的最大数量,默认值是100,即最多获取100个URI 参数,传递0则表示不做限制,解析所有的URI 参数(不推荐)。
local args = ngx.req.get_uri_args(20) -- 最多解析出20个参数
for k,v in pairs(args) do -- 使用pairs函数遍历解析出的参数
ngx.say ("args :", k,”=”, v) -- 逐个输出参数
end
如果有多个同名的参数,那么会存储在数组里,即type(v)==“table” 。
由于ngx.req.get_uri_args解析所有的URI参数,当参数很多而只用其中的某几个时成本就显得较高,这时建议直接使用ngx.var.arg_xxx(即$arg_xxx)。
(2)获取POST 参数:
ngx.req.get_post_args的用法与ngx.req.get_uri_args基本相同,但因为参数位于请求体,所以必须要先调用ngx.req.read_body读取数据,而且还要保证请求体不能存储在临时文件里。
ngx.req.read_body() -- 必须先读取请求体数据
local args = ngx.req.get_post_args(10) -- 然后才能解析参数
(3)改写参数:
函数ngx.req.set_uri_args改写URI 里的参数部分
ngx.req.set_uri_args(args)
它可以接受两种形式的参数,第一种是标准的URI字符串(相当于直接赋值给ngx.var.args )。第二种是Lua表,通常第二种形式用起来更加方便,OpenResty 会自动把表编码转换为规范的参数字符串。
local args = { a = 1 , b = { '#', '$' } } -- 待编码的参数表
ngx. req.set_uri_args(args) -- 调用函数修改URI参数
ngx.say(ngx.var.args) -- 结果是“ a=1&b=%23&b=25%”
有的时候需要手工处理URI参数,OpenResty为此提供了函数ngx.encode_args和ngx.decode_args,前者把表编码成字符串,后者则是反向操作,把字符串解码成Lua表。
local args = { n =1, v=100 } -- 待编码的参数表
local enc = ngx.encode_args (args) -- 编码,结果是“ v=100&n=1 ”
local dec = ngx.decode_args(enc) -- 解码还原成Lua表
5、请求头:
HTTP请求头包含多个Key:Value形式的字段,非常适合用Lua里的表来管理,在OpenResty 里操作起来也很方便。
(1)读取数据:
函数ngx.req.get_headers解析所有的请求头,返回一个表:
local headers= ngx.req.get_headers() -- 解析请求头
for k, v in pairs(headers) do -- 遍历存储头字段的表
ngx.say ( '\t', k, ':', v) -- 逐个输出头字段
end
为了能够在Lua 代码里作为名字使用,头字段在解析后有了两点变化:
1、完全小写化
2、'-'改为'_'
例如:
ngx.say(headers.host) -- “Host”小写化
ngx.say(headers.user_agent) -- “ User-Agent ”,小写化加“_”
不过"[]"的方式允许使用字段的原始形式:
ngx.say(headers['User-Agent'])
ngx.say(headers['Accept'])
与解析URI参数的ngx.req.get_uri_args类似,ngx.req.get_headers也会解析所有的头字段,当只想读取其中的少数字段时建议直接使用ngx.var.http_xxx (即$http_xxx ) 以节约成本。
(2)改写数据:
函数ngx.req.set_header可以改写或新增请求里的头字段
ngx.req.set_header("Accept", "Firefox") -- 改写头字段“ Accept ”
ngx.req.set_header("Metroid", "Prime 4") -- 新增头字段“ Metro id”
删除头字段可以把值置为nil ,或者调用函数ngx.req.clear_header
ngx.req.set_header("Metroid", nil) -- 使用nil删除头字段“ Metroid ”
ngx.req.clear_header("Accept") -- 删除头字段“ Accept ”
6、请求体:
请求体是HTTP请求头之后的数据,通常由POST或PUT方法发送,可以从客户端得到大块的数据。
(1)丢弃数据:
很多时候我们并不关心请求体(例如GET/HEAD方法),调用函数ngx.req.discard_body就可以明确地“丢弃”请求体
ngx.req.discard_body() --显式丢弃请求体
(2)读取数据:
出于效率考虑,OpenResty不会主动读取客户端发迭的请求体数据(除非使用指令“ lua_need_request_body on),读取请求体需要执行下面的步骤:
1. 调用函数ngx.req.read_body ,开始读取请求体数据
2. 调用函数ngx.req.get_body_data获取数据,相当于$request_body
3. 如果得到是nil,可能是数据过大,存放在了磁盘文件里,调用函数ngx.req.get_body_file可以获得相应的临时文件名(相当于$request_body_file)
4. 使用io.*函数打开文件,读取数据(注意是阻塞操作!)
对应Lua代码:
ngx.req.read_body() -- 要求读取请求体数据,同步非阻塞
local data = ngx.req.get_body_data()
if data then -- 在内存中就不是nil ,可以直接使用
ngx.say("body : ", data)
else -- 在磁盘文件里则是nil
local name = ngx.req.get_body_file()
local f = io.open(name, 'r' )
data = f:read("*a") -- 从文件中读取数据, 阻塞操作
f:close()
end
在OpenResty里请求体数据总是先被读入内存,但为了减小内存占用,OpenResty设定了一个限制:8KB ( 32位系统)或16KB ( 64位系统),超过此值的请求体就会存放到硬盘上。可以用指令“ client_body_buffer_size ”改变。通常来说内存的速度要比硬盘快很多,所以应当依据实际情况适当调整,在节约内存的前提下尽量让数据保留在内存中处理,避免慢速的磁盘操作阻塞整个应用。
(3)改写数据:
如果没有显式丢弃请求体,并且已经调用了ngx.req.read_body开始读取数据, 那么就可以用ngx.req.set_body_data或ngx.req.set_body_ file来改写请求体。两个函数的区别是数据来源:前者直接使用字符串,后者使用指定的文件。
ngx.req.set_body_data('yyyy') -- 改写请求体数据
local data = ngx.req.get_body_data() -- 重新读取改写后的请求体数据
ngx.req.set_body_file("/tmp/xxx") -- 从磁盘文件里读取数据改写请求体
local data = ngx.req.get_body_data() -- 重新读取改写后的请求体数据
可以使用下面的函数“逐步”创建一个新的请求体,替代原请求的数据
init_body:开始创建请求体
append_body:向init_body创建的请求体里添加数据
finish_body:完成请求体数据的创建
示例:
ngx.req.init_body() -- 创建一个新的请求体
ngx.req.append_body('aaa') -- 向请求体里添加数据
ngx.req.finish_body() -- 完成请求体数据的创建
local data = ngx.req.get_body_data() -- 重新读取请求体数据,请求体是“aaa”
7、响应头:
HTTP协议里的响应头包括状态行和响应头字段,OpenResty会设置它们的默认值,但我们也可以任意修改。
(1)改写数据:
ngx.status相当于$status , 可以读写响应状态码。如果不显式调用ngx.status 设置状态码,那么它的默认值就是0 ,但最后会转换为标准的ngx.HTTP_OK(即200)
ngx.log(ngx.ERR, ngx.status or "-") -- 获取当前的状态码
ngx.status = ngx.HTTP_ACCEPTED -- 改写状态码为202
表ngx.header(注意不是headers)相当于$sent_http_xxx,可以读取、修改或删
除响应头字段,用法与请求头类似。在添加宇段时"[]“方式会保持名字的原状,而“.”方式会自动把名字里的“_”转换成”-",但大小写不会自动转换
ngx.header['Server'] = 'my openresty' -- 改写Server字段
ngx.header.content_length = 0 -- 相当于['Content -Length']
ngx.header.new_field = 'xxx ’ -- 新增字段'new-field: xxx'
ngx.header.date = nil -- 删除Date字段
函数ngx.resp.add_header可以新增头字段,功能与ngx.header类似,但它不会覆盖同名的字段,而且必须显式加载才能使用
local ngx_resp = require "ngx.resp" -- 显式加载ngx.resp库
ngx_resp.add_header("new_field","yyy") -- 新增同名字段,不会覆盖
此外还有一个函数ngx.resp.get_headers,它的功能与ngx.req.get_headers类似,以表的形式获取当前所有的响应头字段,但多数情况下我们还是应该使用效率更高的ngx.header.xxx或ngx.resp.add_header。
(2)发送数据:
调用函数ngx.send_headers可以显式地发送响应头,但它不是必须的,因为响应头总是在响应体数据之前, OpenResty会在ngx.print或ngx.say之前自动地执行这个操作,所以在代码里通常不应该出现它。
ngx.headers sent是响应头是否己经发送到客户端的标志量,如果响应头己经发送完毕,那么就应该避免再改写ngx.status和ngx.header。
(3)过滤数据:
响应头数据在发送到客户端的“途中”会经过OpenResty的filter阶段,即“header_filter_by_lua”,在这里也可以改写状态码和头字段,它可以配合“"content_by_lua"和"proxy_pass"等指令变更客户端最终收到的数据。
header_filter_by_lua_block { -- 过滤处理响应头信息
if ngx.header.etag then -- 检查是否有ETag字段
ngx.header.etag = nil -- 有则删除ETag字段
end
ngx.header["Cache-Control"] = "max-age = 300" -- 添加Cache-Control字段,要求援存5分钟
content_by_lua与header_filter_by_lua之间的区别关键点是这两者所在的执行阶段:前者是数据的起点、来源,而后者是数据传输的中间点;前者主要作用是生产数据,而后者主要作用是修改数据。而且,在不能使用content_by_lua的情况下(通常是反向代理proxy_pass),header_filter_by_lua更是改写响应数据的唯一手段。
8、响应体:
(1)发送数据:
ngx.print和ngx.say这两个函数会先发送响应头(如果未显式调用ngx.send_headers),然后向客户端发送响应体数据,两者的功能基本相同,但ηgx.say会在数据末尾添加一个换行符,多用于调试和测试。
ngx.print和ngx.say的入口参数很灵活,多个参数或者参数是数组形式可以自动合并,比手动调用table.concat更方便而且效率高。它们也是非阻塞的,OpenResty 内部会使用协程自动处理数据的发送,即使是很大的数据也不会阻塞整OpenResty 进程(但一次发送大块的数据会占用较多的内存,最好拆分成小块后分片发送)。
ngx.print()/ngx.say()
为了提高发送效率,避免发生不必要的系统调用,ngx.print/ngx.say内部使用了缓
冲机制,调用后可能不会立即执行发送动作。函数ngx.flush可以用在发送后要求OpenResty 强制刷新缓冲区,保证数据确实发送到客户端。
ngx.flush()
ngx.flush是一个异步操作,可以传入参数true同步等待刷新操作完成(但仍然是非阻塞的)
local data = {'mario','zelda'}
ngx.say( data) -- 直接发送数组里的数据,自动合并
for ,v in ipairs(data) do -- 遍历数组,分片发送
ngx.print(v) -- 发送一部分数据
ngx.flush(true) -- 刷新缓冲区,同步非阻塞等待发送完成
end
(2)过滤数据:
与header_filter_by_lua类似,响应体数据在发送到客户端的“途中”也会经过filter阶段,即body_filter_by_lua,我们可以在body_filter_by_lua里对响应数据做任意的修改、删除或截断,改写客户端最终收到的数据。
在这个阶段主要使用的功能接口是ngx.arg数组。ngx.arg[1]操作发送的数据,ngx.arg[2]是个bool标志量,表示发送是否己经完成(EOF),即ngx.arg[1]是最后一块数据。
body_filter_by_lua_block { -- 过滤处理响应体数据
if ngx.re.find(ngx.arg[1], 'xxx', "jo") then -- 发现特殊字符串,删除这部分数据
ngx.arg[1] = nil
return
end
if ngx.arg[2] then -- 检查EOF标志位,是否是最后一块
ngx.arg[1] = ngx.arg[1] .. "xx" -- 在数据末尾附加一些额外数据
end
使用body_filter_by_lua在代码里修改可能会导致响应体数据的长度发生变化,为了避免与响应头里的“ Content-Length ”不匹配,最好在header_filter_by_lua或之前的其他执行阶段里把这个字段删除
header_filter_by_lua_block {
ngx.header.content_length = nil
}
9、手动收发数据:
Openresty提供了一个特别的函数ngx.req.socket,可以获得连接客户端的cosocket 对象,直接与客户端通信,对数据收发做更精细的控制。
sock , err = ngx.req.socket(raw) -- 获取客户端cosocket对象
默认情况下ngx.req.socket获得的cosocket对象是只读的,只能接收数据,实现
类似ngx.req.read_body的读取请求体功能,但读取的主动权完全掌握在用户手里。
local sock , err = ngx.req.socket() -- 获取只读的客户端cosocket对象
assert(sock) -- 断言对象是有效的
local len = tonumber(ngx.var.http_content_length) -- 从请求头里获取数据长度
local data = sock:receive(len) -- 手动控制读取指定的字节
如果调用时传递参数true ,那么函数会返回一个拥有完全读写功能的cosocket对象,能够任意向客户端收发数据。但它的发送功能可能会与ngx.send_headers/ngx .print/ngx.say冲突,所以最好先调用ngx.flush(true)清空输出缓冲区,这样之后的数据收发将都由这个cosocket对象来负责
ngx.header.content_length = len -- 设置响应头字段
ngx.send_headers() -- 发送响应头
ngx.flush(true) -- 清空缓冲区,注意必须使用true
local socket , err = ngx.req.socket(true) -- 获取可读写的全功能cosocket对象
sock:send(data) -- 手动发送数据
ngx.req.socket的功能非常灵活,能够实现数据的流式传输(绕过client_body_buffer_size的限制),或者基于HTTP的自定义协议(如Websocket),但它同时也增加了用户的责任,编码需要更多的处理步骤,通常情况下还是使用ngx.req.*系列函数更加方便安全。
10、流程控制:
OpenResty里有四个特别的函数用来控制HTTP 处理流程,包括重定向和提前结束处理:
ngx.redirect(uri, status):标准的301/302重定向跳转
ngx.exec(uri, args):跳转到内部的其他location
ngx.exit(status):立即结束请求的处理
ngx.eof():发送EOF标志,后续不会再有响应数据
(1)重定向请求
函数ngx.redirect执行标准的301/302重定向跳转,它将结束当前的处理过程,跳转到指定的URI重新开始请求处理流程。ngx.redirect对调用的时机有要求,必须在向客户端发送数据之前,也就是在ngx.send_headers/ngx.print/ngx.say之前,通常最好在“rewrite_by_lua”或“access_by_lua”阶段里使用。
ngx.redirect("https://www.github.com") -- 跳转到外部网站,默认是302
ngx.redirect("/new_path", 301) -- 跳转到其他location ,状态码301
ngx.exec 的功能类似ngx.redirect,但它只能跳转到本server 内部的其他location,相当于“执行”了另一个location里的功能。利用它可以把处理流程划分成“流水线”式的多个节点,每个节点集中一些业务逻辑,然后用ngx.exec 跳转到下一个节点继续处理。
location = /exec { -- 一个专门用来跳转的location
rewrite_by_lua_block { -- 在rewrite阶段执行Lua代码
ngx.req.set_header("Exec","True") -- 改写请求头
ngx.exec("/xxx", ngx.var.args) -- 内部跳转到其他location继续处理
}
}
(2)终止请求:
函数ngx.exit可以在任意的执行阶段调用,立即结束请求的处理,返回的状态码可以用参数指定
0:仅结束本阶段的处理流程,直接进入下一个阶段
>200:结束整个请求处理流程,跳过后续阶段(filter和log除外)
如果在处理过程中发现有错误,就有必要调用ngx.exit及时结束处理流程,向客户端报告错误原因。
if not ngx.var.arg_hello then -- 检查URI 里的参数
ngx.exit(400) -- 如果缺少必要的参数则报400 错误
end
ngx.eof是另一种结束请求处理的方式,它会向客户端发送EOF标志(即ngx.arg[2]
==true),表示后续不会再有响应数据发送,但代码逻辑并不结束,仍然会继续执行。使用ngx.eof可以尽早返回给客户端响应数据,然后再执行统计、存储等收尾工作,减少客户端感知的等待时间。不过ngx.eof后的工作不直过多,因为它毕竟占用了请求的实际处理时间,对于与请求无关的收尾工作建议放在log_by_lua里执行,或者使用ngx.timer.at创建一个后台任务延后处理。
11、检测断连:
在HTTP应用服务里通常是由服务器端主动关闭连接,但有时也会发生客户端主动断连的情况,如果不正确处理就有可能会导致服务器的资源无法及时回收。OpenResty 可以捕获这种“意外事件”,使用检测断连功能要是先使用配置“ lua_check_client_abort on ”,然后编写一个处理客户端断连的回调函数handler ,利用Lua 函数的闭包特性访问外部的各种变量,执行必要的资源清理工作。最后需要在代码里调用ngx.on_abort注册函数,当断连事件发生时OpenResty 就会回调执行handler。
local function cleanup() -- 断连时的回调函数
ngx.log(ngx.ERR, "client abort")
...
ngx.exit(444) -- 结束请求,使用特殊的状态码
end
local ok, err = ngx.on_abort(cleanup) -- 注册断连回调函数
当客户端主动断连(例如Ctrl+C)时就会执行函数cleanup,保证资源能够正确释放。