一、OpenResty是什么
OpenResty就是嵌入了LuaJIT VM的Nginx;LuaJIT即采用C语言写的Lua代码的解释器。通过 OpenResty,我们可以把 nginx 的各种功能进行自由拼接, 重要的是,开发门槛并不高,我们只需要熟悉简单灵巧的Lua 语言和nginx基础知识就可以上手开发简单功能了。
OpenResty让我们可以更加从容的对nginx功能进行扩展。说到nginx功能扩展,大家可能还想到了nginx模块编程。Nginx C Module编程学习成本比较高(有兴趣可以简单了解一下,参考链接nginx模块编程),连接一些外部存储服务(比如redis、Postgres、Memcached、mysql等等)比较困难,另外模块代码改动后要重新编译;而Openresty因为可以使用lua脚本,很多的lua库都可以引入,而且每次修改脚本后直接reload即可生效,相对来说就要简单和灵活了不少。
lua脚本这里不多说了,附上一个W3C的基础教程地址:W3C Lua 教程
二、Lua脚本与Nginx交互顺序
lua执行指令都包含在nginx的11个步骤之中了,相应的处理阶段可以做插入式处理,即可插拔式架构,不过ngx_lua并不是所有阶段都会运行的;另外指令可以在http、server、location几个范围进行配置,具体如下:
指令 | 所处处理阶段 | 使用范围 | 解释 |
init_by_lua init_by_lua_block init_by_lua_file | loading-config | http | nginx Master进程加载配置时执行; 通常用于初始化全局配置/预加载Lua模块 |
init_worker_by_lua init_worker_by_lua_block init_worker_by_lua_file | starting-worker | http | 每个Nginx Worker进程启动时调用的计时器,如果Master进程不允许则只会在init_by_lua之后调用; 通常用于定时拉取配置/数据,或者后端服务的健康检查 |
set_by_lua set_by_lua_block set_by_lua_file | rewrite | server,server if, location,location if | 设置nginx变量,可以实现复杂的赋值逻辑;此处是阻塞的,Lua代码要做到非常快; |
rewrite_by_lua rewrite_by_lua_block rewrite_by_lua_file | rewrite tail | http,server,location,location if | rrewrite阶段处理,可以实现复杂的转发/重定向逻辑; |
access_by_lua access_by_lua_block access_by_lua_file | access tail | http,server,location,location if | 请求访问阶段处理,用于访问控制 |
content_by_lua content_by_lua_block content_by_lua_file | content | location,location if | 内容处理器,接收请求处理并输出响应 |
header_filter_by_lua header_filter_by_lua_block header_filter_by_lua_file | output-header-filter | http,server,location,location if | 设置header和cookie |
body_filter_by_lua body_filter_by_lua_block body_filter_by_lua_file | output-body-filter | http,server,location,location if | 对响应数据进行过滤,比如截断、替换。 |
log_by_lua log_by_lua_block log_by_lua_file | log | http,server,location,location if | log阶段处理,比如记录访问量/统计平均响应时间 |
这部分内容参考了:
玩转 Nginx 之:使用 Lua 扩展 Nginx 功能_阳光梦的专栏-CSDN博客_lua nginx
三、永远的hello world
(1)直接在nginx.conf内编写代码
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 80;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("<p>hello world</p>")
}
}
}
}
浏览器地址栏输入 localhost 查看效果如下图:
(2)引入lua文件
lua文件位置及内容如下图:
nginx.conf配置如下(请查看 8000 端口下配置):
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 80;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("<p>hello world</p>")
}
}
}
server {
listen 8000;
location / {
default_type text/html;
content_by_lua_file /lua/work/helloworld.lua;
}
}
}
浏览器查看效果:
(3)批量引用lua脚本(引用lualib)
如果项目比较大,有时候会编写一些自己项目的lua脚本库,那么我们可以使用如下方式进行引入
举例:
自己的lualib存放位置如下“mylib.lua”
在nginx.conf配置文件中引入 ----- lua_package_path 'lualib/mylib/?.lua;;';
这里的引入规则可以参考Lua中的模块(module)和包(package)详解
nginx.conf配置文件如下:
worker_processes 1;
events {
worker_connections 1024;
}
http {
lua_package_path 'lualib/mylib/?.lua;;';
server {
listen 80;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("<p>hello world</p>")
}
}
}
server {
listen 8000;
location / {
default_type text/html;
content_by_lua_file /lua/work/helloworld.lua;
}
}
server {
listen 8080;
location / {
default_type text/html;
content_by_lua_file /lua/work/my_helloworld.lua;
}
}
}
mylib.lua的内容(定义了一个常量版本号,定义了一个方法say_hello_world())
local _M = { _VERSION = '1.0.0' }
function _M.say_hello_world()
ngx.say("<p>hello world!!!</p>")
end
return _M
我们在my_helloworld.lua(参看nginx.conf中8080端口的配置)这个脚本中使用这个mylib中定义的方法,my_helloworld.lua脚本内容如下:
local mylib = require 'mylib'
ngx.say("<p>"..mylib._VERSION.."</p>")
mylib.say_hello_world()
浏览器查看结果:
四、常用的Nginx Lua API
进行nginx lua扩展时,我们需要接收请求、处理并输出响应。接收请求后我们经常需要获取如请求参数、请求头、Body体等信息用于信息分类、信息存储、数据处理、反向代理等等,处理完成后需要进行响应状态码、响应头和响应内容体的输出。这一整个过程中,我们除了学习基础的lua语法之外,还需要熟悉nginx lua api,nginx lua api中包含了各种现成可用的快捷功能、接口,可以免去我们很多麻烦。这里我只列举几种常用的,需要学习更全面的可以查看 Lua | NGINX
http协议内容:
ngx.req.get_headers:获取请求头,默认只获取前100,如果想要获取所以可以调用ngx.req.get_headers(0);获取带中划线的请求头时请使用如headers.user_agent这种方式;如果一个请求头有多个值,则返回的是table;
ngx.req.get_uri_args:获取url请求参数,其用法和get_headers类似;ngx.req.get_post_args:获取post请求内容体,其用法和get_headers类似,须提前调用ngx.req.read_body()来读取body体;
ngx.req.get_body_data:为解析的请求body体内容字符串。
ngx.req.get_method():获取请求方法 返回GET POST PUT等等请求方法
ngx.req.http_version():获http协议版本号
日志打印:
ngx.log(ngx.ERR,'hello world') :ngx.ERR为日志级别,'hello world'为日志内容
ngx日志级别分为了以下9种,级别从高到低:
ngx.STDERR
ngx.EMERG
ngx.ALERT
ngx.CRIT
ngx.ERR
ngx.WARN
ngx.NOTICE
ngx.INFO
ngx.DEBUG
response响应:
ngx.header:输出响应头
ngx.print:输出响应内容体
ngx.say:通ngx.print,但是会最后输出一个换行符
ngx.exit:指定状态码退出
示例代码来一个:
local cjson = require "cjson"
-- 日志打印
ngx.log(ngx.STDERR,'1 hello world')
ngx.log(ngx.EMERG,'2 hello world')
ngx.log(ngx.ALERT,'3 hello world')
ngx.log(ngx.CRIT,'4 hello world')
ngx.log(ngx.ERR,'5 hello world')
ngx.log(ngx.WARN,'6 hello world')
ngx.log(ngx.NOTICE,'7 hello world')
ngx.log(ngx.INFO,'8 hello world')
ngx.log(ngx.DEBUG,'9 hello world')
--请求方法
ngx.say("ngx.req.get_method : ",ngx.req.get_method(), "<br/>")
--请求的http协议版本
ngx.say("ngx.req.http_version : ",ngx.req.http_version(), "<br/>")
ngx.say("<br/>")
ngx.say("<br/>")
--get请求uri参数
ngx.say("get uri args begin", "<br/>")
local uri_args = ngx.req.get_uri_args()
for k, v in pairs(uri_args) do
ngx.say(k, ": ", v, "<br/>")
end
ngx.say("get uri args end", "<br/>")
ngx.say("<br/>")
ngx.say("<br/>")
--post请求参数
ngx.req.read_body()
ngx.say("post args begin", "<br/>")
local post_args = ngx.req.get_post_args()
for k, v in pairs(post_args) do
ngx.say(k, ": ", v, "<br/>")
end
-- 处理post参数
local p_body_data = ngx.req.get_body_data()
local json_body = cjson.decode(p_body_data)
if json_body ~= nil then
ngx.say("json body : ")
for jk,jv in pairs(json_body) do
ngx.say(jk, ": ", jv, ",")
end
end
ngx.say("post args end", "<br/>")
ngx.say("<br/>")
ngx.say("<br/>")
--请求头
local headers = ngx.req.get_headers()
ngx.say("headers begin", "<br/>")
for k,v in pairs(headers) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ","))
else
ngx.say(k, " : ", v, "<br/>")
end
end
ngx.say("headers end", "<br/>")
上面代码的日志输出 如下图:(error.log日志默认的日志级别是ngx.ERR)
网页上http get 请求后结果 如下图:
postman post 请求结果 如下图:
到这里大家会发现一个问题,通过调用ngx.req.get_post_args()获取参数时,参数被openresty处理为{"json参数":true}的格式,这不是我们想要的结果。于是我们通过ngx.req.get_body_data()拿到body的json字符串,使用cjson反序列化后,得到想要的结果。
基本的介绍就到这里了,我们用lua可以做很多事情,上面只列举了九牛一毛。对于如何连接redis,怎么读写mysql,又怎么读写文件,怎么加缓存,等等等等 还有很多。
冰山一角,任重而道远,共勉!