在nginx.conf中,可以执行lua代码。lua模块可以看作是将一个class单独存成文件。
所以学习OpnenResty要学习两部分语法,一方面是Nginx内置绑定变量和函数,另一方面是Lua语法和Lua针对Nginx实现的类库。
所以以下就从这两方面进行学习和总结。
关于Nginx的介绍很多,觉得这个挺好:agentzh 的 Nginx 教程,但是我还没看完。
先介绍Nginx内置绑定变量。
名称 | 说明 |
---|---|
$arg_name | 请求中的name参数 |
$args | 请求中的参数 |
$binary_remote_addr | 远程地址的二进制表示 |
$body_bytes_sent | 已发送的消息体字节数 |
$content_length | HTTP请求信息里的”Content-Length” |
$content_type | 请求信息里的”Content-Type” |
$document_root | 针对当前请求的根路径设置值 |
$document_uri | 与$uri相同; 比如 /test2/test.php |
$host | 请求信息中的”Host”,如果请求中没有Host行,则等于设置的服务器名 |
$hostname | 机器名使用 gethostname系统调用的值 |
$http_cookie | cookie 信息 |
$http_referer | 引用地址 |
$http_user_agent | 客户端代理信息 |
$http_via | 最后一个访问服务器的Ip地址。 |
$http_x_forwarded_for | 相当于网络访问路径 |
$is_args | 如果请求行带有参数,返回“?”,否则返回空字符串 |
$limit_rate | 对连接速率的限制 |
$nginx_version | 当前运行的nginx版本号 |
$pid | worker进程的PID |
$query_string | 与$args相同 |
$realpath_root | 按root指令或alias指令算出的当前请求的绝对路径。其中的符号链 |
$remote_addr | 客户端IP地址 |
$remote_port | 客户端端口号 |
$remote_user | 客户端用户名,认证用 |
$request | 用户请求 |
$request_body | 这个变量(0.7.58+)包含请求的主要信息。在使用proxy_pass或fastcgi_pass指令的location中比较有意义 |
$request_body_file | 客户端请求主体信息的临时文件名 |
$request_completion | 如果请求成功,设为”OK”;如果请求未完成或者不是一系列请求中最后一部分则设为空 |
$request_filename | 当前请求的文件路径名,比如/opt/nginx/www/test.php |
$request_method | 请求的方法,比如”GET”、”POST”等 |
$request_uri | 请求的URI,带参数 |
$scheme | 所用的协议,比如http或者是https |
$server_addr | 服务器地址,如果没有用listen指明服务器地址,使用这个变量将发起一次系统调用以取得地址(造成资源浪费) |
$server_name | 请求到达的服务器名 |
$server_port | 请求到达的服务器端口号 |
$server_protocol | 请求的协议版本,”HTTP/1.0”或”HTTP/1.1” |
$uri | 请求的URI,可能和最初的值有不同,比如经过重定向之类的 |
如何使用内置的绑定变量,通过一个例子来看
server {
listen 8080;
server_name _;
location /test {
default_type "text/html";
echo $host;
}
}
当访问
http://localhost:8080/test #网页会输出"localhost"
其他的绑定变量的使用也是一样的
下面介绍如何使用Lua代码。有两种方式。第一种,直接在server里的location下,嵌入到
content_by_lua_block{...}
比如:
location /test{
default_type "text/html";
content_by_lua_block {
local res = ngx.location.capture('/print_param',
{
method = ngx.HTTP_POST,
args = {a = 1, b = '2&'},
body = 'c=3&d=4%26'
}
)
ngx.say(res.body)
}
}
在content_by_lua_block下的就是lua语法了。
第二种方式为文件引入。这种方式更符合模块化的思想,引入方式只需要将
content_by_lua_block{}
替换为
content_by_lua_file /usr/openresty/example/test.lua
然后去看test.lua都有什么
test.lua
-------
ngx.location.capture('/print_param',
{
method = ngx.HTTP_POST,
args = {a = 1, b = '2&'},
body = 'c=3&d=4%26'
}
)
ngx.say(res.body)
可以看到就是第一种方式里的lua代码部分,这说明文件方式引用,其实就是一种插入代码的形式。这跟lua的模块思想是一样的。可以原封不动的放过来,拆出来一个文件更多的是模块化思想。
我觉得Lua的语法可以去学习Lua简明教程,这里只需要介绍与Nginx相关的语法。
ngx.say和ngx.print均是向屏幕输出内容,知识ngx.say会在内容后多加一个回车符,类似与print与println的区别。
nginx中的set指令,lua中有对应的set_by_lua,可以接收lua代码的返回值。
set %name "lisi";
set_by_lua_file $name /usr/openresty/example/lua/test1.lua
Nginx API被封装ngx和ndk两个package中。
比方ngx.var.NGX_VAR_NAME能够訪问Nginx变量。这里着重介绍一下ngx.location.capture和ngx.location.capture_multi。
ngx.location.capture
语法:res= ngx.location.capture(uri, options?) 用于发出一个同步的,非堵塞的Nginxsubrequest(子请求)。
能够通过Nginx subrequest向其他location发出非堵塞的内部请求。这些location能够是配置用于读取目录的,也能够是其他的C模块,比方ngx_proxy, ngx_fastcgi, ngx_memc, ngx_postgres, ngx_drizzle甚至是ngx_lua自己。 Subrequest仅仅是模拟Http接口,并没有额外的Http或者Tcp传输开销,它在C层次上执行,很高效。Subrequest不同于Http 301/302重定向,以及内部重定向(通过ngx.redirection)。
#配置:
location =/other {
ehco 'Hello, world!';
}
# Lua非堵塞IO
location =/lua {
content_by_lua '
local res = ngx.location.capture("/other")
if res.status == 200 then
ngx.print(res.body)
end
';
}
输出:
$ curl 'http://localhost/lua'
$ Hello, world!
实际上,location能够被外部的Http请求调用,也能够被内部的子请求调用。每一个location相当于一个函数,而发送子请求就类似于函数调用。并且这样的调用是非堵塞的,这就构造了一个很强大的变成模型,后面我们会看到怎样通过location和后端的memcached、redis进行非堵塞通信。
ngx.location.capture_multi
语法:res1,res2, … = ngx.location.capture_multi({ {uri, options?}, {uri, options?}, …}) 与ngx.location.capture功能一样,能够并行的、非堵塞的发出多个子请求。这种方法在全部子请求处理完毕后返回。而且整个方法的执行时间取决于执行时间最长的子请求,并非全部子请求的执行时间之和。
配置
# 同一时候发送多个子请求(subrequest)
location =/moon {
ehco 'moon';
}
location =/earth {
ehco 'earth';
}
location =/lua {
content_by_lua '
local res1,res2 = ngx.location.capture_multi({ {"/moon"}, {"earth"} })
if res1.status == 200 then
ngx.print(res1.body)
end
ngx.print(",")
if res2.status == 200 then
ngx.print(res2.body)
end
';
}
输出
$ curl 'http://localhost/lua'
$ moon,earth
在Lua代码中的网络IO操作仅仅能通过Nginx Lua API完毕。假设通过标准Lua API会导致Nginx的事件循环被堵塞,这样性能会急剧下降。在进行数据量相当小的磁盘IO时能够採用标准Lua io库,可是当读写大文件时这样是不行的,由于会堵塞整个NginxWorker进程。为了获得更大的性能。强烈建议将全部的网络IO和磁盘IO托付给Nginx子请求完毕(通过ngx.location.capture)。以下通过访问/html/index.html这个文件。来測试将磁盘IO托付给Nginx和通过Lua io直接访问的效率。通过ngx.location.capture托付磁盘IO:
配置
location / {
internal;
root html;
}
location /capture {
content_by_lua '
res = ngx.location.capture("/")
echo res.body
';
}
通过标准lua io访问磁盘文件:
配置:
location /luaio{
content_by_lua '
local io = require("io")
local chunk_SIZE = 4096
local f = assert(io.open("html/index.html","r"))
while true do
local chunk = f:read(chunk)
if not chunk then
break
end
ngx.print(chunk)
ngx.flush(true)
end
f:close()
';
}
所以,在Lua中进行各种IO时。都要通过ngx.location.capture发送子请求托付给Nginx事件模型,这样能够保证IO是非堵塞的。