Openresty资料之http服务,


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,保证资源能够正确释放。

相关内容

    暂无相关文章