Nginx框架之Lua拓展
Posted 踩踩踩从踩
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nginx框架之Lua拓展相关的知识,希望对你有一定的参考价值。
目录
Lua脚本简述
nginx三大核心功能,包含静态资源、反向代理、api模块扩展,对于lua脚本的扩展,就是api模块扩展的一部分;并且nginx可以通过lua脚本直接调用redis服务器;
Lua脚本简述
lua官网:
应用在图像处理这些。
- 1. 许多工业应用 (例如, Adobe的Photoshop Lightroom)
- 2. 重点是嵌入式系统(例如, 巴西的数字电视的 Ginga中间件)
- 3. Lua目前 是游戏中领先的脚本语言,在游戏 (例如, 魔兽世界和愤怒的小鸟)中使用。
脚本特点
- 快,Lua是解释脚本语言领域中最快的语言。
- Lua是便携式的,在具有标准C编译器的所有平台中构建成开箱即用,可运行各种Unix和Windows, 移动设备(运行android,ios,BREW,Symbian,Windows Phone),嵌入式微处理器(如ARM和Rabbit,适用于Lego MindStorms等应用程序),IBM大型机等。
- 可嵌入,Lua是一种快速语言引擎,占用空间小,可以轻松嵌入到应用程序中。
- 简单而强大,提供实现功能的*元机制*,而不是直接在语言中提供大量功能。Lua不是纯粹的面对 象语言,提供了实现类和继承的元机制。
- 小,源包含大约24000行C,包含源代码和文档的Lua 5.3.5的tar包需要297K压缩和1.2M未压缩。 免费,MIT许可证,可以用于任何目的,包括商业目的,完全免费。
比js语言更加广泛。轻量级语言不能做的事情,没有什么大的框架,例如java中spring,这也是它其中的一个缺点把
安装Lua
比较简单
curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz
tar zxf lua-5.3.5.tar.gz
cd lua-5.3.5
make linux test
依赖不多,直接下载就行。
使用打印 hello world
$ lua
Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio
> print("hello, world")
hello, world
lua小例子
- 定义一个外界可见的表
- 受保护的自定义数据内容
- function给_M定义一个方法,get_age,用来返回data中的数据
- 向外界暴露表
-- 定义一个外界可见的表
local _M = {}
-- 受保护的自定义数据内容
local data = {
dog = 5,
cat = 3,
pig = 1,
}
-- function给_M定义一个方法,get_age,用来返回data中的数据
function _M.get_age(name)
return data[name]
end -- end表示方法结束
-- 向外界暴露表
return _M
这个和js中的方法就很像了。
利用lua脚本实现 阶乘
-- defines a factorial function
function fact (n)
if n == 0 then
return 1
else
return n * fact(n-1)
end
end
print("enter a number:")
a = io.read("*number") -- read a number
print(fact(a))
要运行 ,直接 使用 lua 文件就能运行起来
语法比Java要简略,缩进对齐,无大括号分号
Nginx增加Lua执行模块
Nginx嵌入Lua脚本语言
ngx_lua模块
Nginx嵌入Lua脚本语言
安装编译Nginx
- 安装依赖项
yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel
# lua_jit
wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz
tar -xvf LuaJIT-2.0.5.tar.gz
cd LuaJIT-2.0.5
make install
下载安装nginx和依赖项
cd ~
# nginx
wget http://nginx.org/download/nginx-1.14.2.tar.gz
tar -xvf nginx-1.14.2.tar.gz
# ndk
wget https://github.com/simplresty/ngx_devel_kit/archive/v0.3.0.zip
unzip v0.3.0.zip
# ngx_lua 模块
wget https://github.com/openresty/lua-nginx-module/archive/v0.10.14.zip
unzip v0.10.14.zip
# 安装nginx
cd nginx-1.14.2
./configure --prefix=/usr/local/nginx-1.14.2 \\
--add-module=../ngx_devel_kit-0.3.0 \\
--add-module=../lua-nginx-module-0.10.14
- 测试lua模块是否安装成功
- 创建一个文件夹存储lua脚本
mkdir /usr/local/nginx-1.14.2/lua_scripts
- 创建mydata.lua
-- mydata.lua
local _M = {}
local data = {
dog = 3,
cat = 4,
pig = 5,
}
function _M.get_age(name)
return data[name]
end
return _M
- nginx.conf文件
lua_package_path "/usr/local/nginx-1.14.2/lua_scripts/?.lua;;";
server {
...
location /lua {
content_by_lua_block {
local mydata = require "mydata"
ngx.say(mydata.get_age("dog"))
}
}
可能的错误
# 1、动态库找不到
./sbin/nginx: error while loading shared libraries: libluajit-5.1.so.2: cannot open shared object file: No such file or directory
# 解决办法:
echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf
ldconfig
# 2、warn提示
nginx: [alert] detected a LuaJIT version which is not OpenResty's; many optimizations will be disabled and performance will be compromised (see https://github.com/openresty/luajit2 for OpenResty's LuaJIT or, even better, consider using the OpenResty releases from https://openresty.org/en/download.html)
# 告诉你,你不要用这个luajit版本,可以用openresty提供的luajit优化版本,或者干脆直接用openresty
安装lua_redis
wget https://codeload.github.com/openresty/lua-resty-redis/zip/master
unzip master
cd lua-resty-redis-master
make && make install
都是lua集成的组件。
OpenResty 目录
包括nginx目录等。
ngx_lua支持的指令
指令分为配置指令、控制指令。
控制指令分为两种方式:
lua脚本块*_by_lua_block
lua脚本文件*_by_lua_file
Lua脚本执行顺序,如右图从上至下:初始化、重写/访问、内容处理、日志输出四个阶段。
在OpenResty中演示ngx_lua的指令
设置由set_by_lua,content_by_lua等指定的脚本使用的Lua模块搜索路径。类似java的classPath
预加载json模块
在location、location if上下文中使用。充当“内容处理程序”并执行指定的lua脚本内容,Lua代码可以进行API调用,并在独立的全局环境(即沙箱)中作为新的衍生协程执行
worker_processes 1;
events {
worker_connections 1024;
}
http {
# 设置由set_by_lua, content_by_lua等指定的脚本使用的Lua模块搜索路径。类似java的classPath
# 路径字符串采用标准的Lua路径形式,;; 可用于代表原始搜索路径。
lua_package_path "/usr/local/openresty/lua_scripts/?.lua;;";
# 申请一个名为dogs的10m字典内存,共享在所有工作进程中
lua_shared_dict dogs 10m;
# 在服务器启动时预加载Lua模块,并利用现代操作系统的写时复制(COW)优化。
init_by_lua_block {
-- 预加载json模块
require "cjson"
-- 操作共享内存
local dogs = ngx.shared.dogs;
dogs:set("Tom", 56)
}
server {
listen 8080;
location /lua {
default_type text/html;
# 在location、location if上下文中使用。充当“内容处理程序”并执行指定的lua脚本内容,
# Lua代码可以进行API调用,并在独立的全局环境(即沙箱)中作为新的衍生协程执行
content_by_lua_block {
local mydata = require "mydata"
ngx.say(mydata.get_age("dog"))
-- require关键字会返回init_by_lua_block中已经预加载了cjson模块
ngx.say(require "cjson".encode{dog = 5, cat = 6})
-- 读取共享内存中的信息
local dogs = ngx.shared.dogs;
ngx.say(dogs:get("Tom"))
}
}
}
}
在运行的时候,需要去掉 nginx目录在运行
打造高性能后端接口
OpenResty Redis模块
要使用Redis非常简单
-- 引入Redis模块
local redis = require("resty.redis")
--创建实例
local red = redis:new()
--建立连接
local ok, err = red:connect(“127.0.0.1”, 6379)
--调用API进行处理
ok, err = red:set("msg", "hello world")
local resp, err = red:get("msg")
这里 用local进行申请变量。 这是lua脚本 和js语言基本一致的地方。
这个模块要保证在linux上客户端存在redis,然后运行lua脚本。
在OpenResty 中 lualib /resty 中放在 这个目录下面的
- 在写lua脚本时需要引用 默认就是 lualib
local redis = require("resty.redis")
- 创建实例 建立连接,通过new方式
--创建实例
local red = redis:new()
--设置超时(毫秒)
red:set_timeout(1000)
--建立连接
local ip = "127.0.0.1"
local port = 6379
local ok, err = red:connect(ip, port)
- 发送数据并判断是否成功
if not ok then
ngx.say("connect to redis error : ", err)
return close_redis(red)
end
--调用API进行处理
ok, err = red:set("msg", "hello world")
if not ok then
ngx.say("set msg error : ", err)
return close_redis(red)
end
- 关闭redis
local function close_redis(red)
if not red then
return
end
--释放连接(Redis连接池实现)
local pool_max_idle_time = 10000 --毫秒
local pool_size = 100 --连接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx.say("set keepalive error : ", err)
end
end
- 得到的数据为空处理
--得到的数据为空处理
if resp == ngx.null then
resp = '' --比如默认值
end
ngx.say("msg : ", resp)
close_redis(red)
对于redis操作,lua脚本是没有事务的支持的。
在 nginx_lua_lib.conf配置上对应的location
请求得到数据
使用场景
降低tomcat的压力,非常有用的。根据不会走到web服务器上。 增大请求这些,是构建百万连接的重要一部分
OpenResty mysql模块
也是一样的 加载 mysql的驱动
local mysql = require("resty.mysql")
创建实例
local db, err = mysql:new()
if not db then
ngx.say("new mysql error : ", err)
return
end
设置超时时间(毫秒)
db:set_timeout(1000)
配置建立连接
local props = {
host = "127.0.0.1",
port = 3306,
database = "mysql",
user = "root",
password = "123456"
}
local res, err, errno, sqlstate = db:connect(props)
if not res then
ngx.say("connect to mysql error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
删除表
local drop_table_sql = "drop table if exists test"
res, err, errno, sqlstate = db:query(drop_table_sql)
if not res then
ngx.say("drop table error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
创建表
local create_table_sql = "create table test(id int primary key auto_increment, ch varchar(100))"
res, err, errno, sqlstate = db:query(create_table_sql)
if not res then
ngx.say("create table error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
关闭连接
local function close_db(db)
if not db then
return
end
db:close()
end
这里有个点安全机制,防止sql注入
--防止sql注入
local ch_param = ngx.req.get_uri_args()["ch"] or ''
--使用ngx.quote_sql_str防止sql注入
local query_sql = "select id, ch from test where ch = " .. ngx.quote_sql_str(ch_param)
res, err, errno, sqlstate = db:query(query_sql)
if not res then
ngx.say("select error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
for i, row in ipairs(res) do
for name, value in pairs(row) do
ngx.say("select row ", i, " : ", name, " = ", value, "<br/>")
end
end
OpenResty http模块
OpenResty默认没有提供Http客户端,需要使用第三方提供,从github上搜索相应的客户端,比如lua-resty-http
cd /usr/local/openresty/lualib/resty/
sudo wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http_headers.lua
sudo wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http.lua
这个模块可以调用第三方的服务。
local http = require("resty.http")
--创建http客户端实例
local httpc = http.new()
local resp, err = httpc:request_uri("http://www.baidu.com", {
method = "GET",
path = "/s?wd=123",
headers = {
["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36"
}
})
if not resp then
ngx.say("request error :", err)
return
end
--获取状态码
ngx.status = resp.status
--获取响应头
for k, v in pairs(resp.headers) do
if k ~= "Transfer-Encoding" and k ~= "Connection" then
ngx.header[k] = v
end
end
--响应体
ngx.say(resp.body)
httpc:close()
Lua模板渲染器
动态web网页开发是Web开发中一个常见的场景,Lua中也有许多模板引擎,我们通过lua-resty-template来完成动态页面的渲染。
cd /usr/local/openresty/lualib/resty/
wget https://raw.githubusercontent.com/bungle/lua-resty-template/master/lib/resty/template.lua
mkdir /usr/local/openresty/lualib/resty/html
cd /usr/local/openresty/lualib/resty/html
wget https://raw.githubusercontent.com/bungle/lua-resty-template/master/lib/resty/template/html.lua
动态生成网页模板。
使用
nginx配置文件,添加模板解析路径
#首先匹配nginx下面的模板目录
set $template_location "/templates";
#然后匹配指定的模板根目录,我们这里是/usr/local/openresty/lua_scripts
set $template_root "/usr/local/openresty/lua_scripts";
#或者通过root指令来切换路径
root /usr/local/openresty/lua_scripts;
lua内容,html_template.lua
local template = require("resty.template")
local context = {
title = "lua模板渲染",
name = "hash同学",
description = "<script>alert(1);</script>",
age = 20,
hobby = {"电影", "音乐", "阅读"},
score = {语文 = 90, 数学 = 80, 英语 = 70},
score2 = {
{name = "语文", score = 90},
{name = "数学", score = 80},
{name = "英语", score = 70},
}
}
template.render("resty_template.html", context)
进行解析生成模板。
Nginx非阻塞与Lua协程的绝配
-- foo本身是个协程,由nginx调用
function foo()
-- 此处由nginx发起db的连接请求,因为异步这里先暂停协程
users = db.query('users', {name: name});
-- 当请求有响应得到处理时,继续执行协程,这些才运行
deal_with(users);
end
Lua协程
coroutine.create(f)
创建一个新的协同程序并返回一个类型为thread的对象,并不启动协程。
coroutine.resume(co)
开始或继续执行协同程序co
coroutine.yield
暂停执行调用协程,让出CPU
coroutine.wrap(f)
类似create方法,创建一个协程但它不是返回协程本身,而是返回一个函数,当被调用时,它恢复协程
Nginx的API生成页面
达到对一个商品页面的实现。
以上是关于Nginx框架之Lua拓展的主要内容,如果未能解决你的问题,请参考以下文章
实时统计 nginx 状态的 lua 拓展ngx_lua_reqstatus
重要Nginx模块之————Lua的Nginx API 常量以及参数介绍 (Lua-Nginx-Module 模块)
nginx+lua+redis实现post请求接口之黑名单(一)
重要Nginx模块之————Lua-Resty-Redis的参数介绍 (Lua-Nginx-Module 模块的Redis客户端驱动程序)