Lua脚本语言开发说明手册

  |   2,714 浏览

1、 前言

Lua是一个小巧的脚本语言。是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组,由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo所组成并于1993年开发。 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行。Lua并没有提供强大的库,这是由它的定位决定的。所以Lua不适合作为开发独立应用程序的语言。Lua 有一个同时进行的GIT项目,提供在特定平台上的即时编译功能。

Lua脚本可以很容易的被C/C代码调用,也可以反过来调用C/C的函数,这使得Lua在应用程序中可以被广泛应用。不仅仅作为扩展脚本,也可以作为普通的配置文件,代替XML,ini等文件格式,并且更容易理解和维护。Lua由标准C编写而成,代码简洁优美,几乎在所有操作系统和平台上都可以编译,运行。一个完整的Lua解释器不过200k,在目前所有脚本引擎中,Lua的速度是最快的。这一切都决定了Lua是作为嵌入式脚本的最佳选择。

2、 搭建LUA运行环境

运行环境,目前我们采用的是openresty集成包,内含nginx及大量常用的lua库。

安装Openresty

A、安装openresty依赖包:yum install pcre-devel openssl-devel(或者手动下载安装,命令:rpm –ivh ****rpm –nodeps)

B、编译安装官方网站下载下来的源码:

tar -xvf openresty-1.11.2.1.tar.gz

cd openresty-1.11.2.1

./configure

gmake && gmake install

安装后默认主目录在:/usr/local/openresty路径

Nginx服务主目录在:/usr/local/openresty/nginx

Lua脚本主目录在:/usr/local/openresty/lualib

一般我们常用到的公用Lua脚本库都存放在目录:/usr/local/openresty/lualib/resty

配置Nginx指向lua脚本

A、 修改nginx配置文件:vi /usr/local/openresty/nginx/conf/nginx.conf

新增一条代理规则:

location /lottery {

    content_by_lua_file /usr/local/openresty/nginx/lua/lottery.lua;

}
说明:拦截http://127.0.0.1:80/lottery/****请求,将请求指向lottery.lua脚本,让lottery.lua脚本处理该请求并返回相应内容。

在这里我们只编写一个简单的lua脚本,将代码:ngx.say(“HelloWord”)

保存在上面目录的lottery.lua文件中。

B、 为了方便本地调试在配置文件中的server节点下新增一条配置:

lua_code_cache off;

说明:取消lua脚本的缓存,在调试的过程中,修改lua脚本时就能实时生效,无需反复重启nginx。

运行Nginx测试lua脚本

Nginx运行程序目录在:/usr/local/openresty/nginx/sbin下。指令:

运行:./nginx

重启:./nginx -s reload

停止:./nginx -s stop

启动后后台输出日志在:/usr/local/openresty/nginx/logs下。打开后台输出日志:

tail -f error.log

在浏览器中访问nginx服务:

http://127.0.0.1:80/lottery

浏览器上就会看到上面我们编辑的lua脚本返回的HelloWord信息。

此时我们查看刚打开的后台输出日志无异常,如果自己编写的lua脚本报错,浏览器会返回500错误,此时后台日志会打印lua脚本报错详细信息。

到此我们就完成了对lua脚本的运行测试了。

3、 LUA脚本语法规范说明

Lua脚本详细语法可以参考:http://www.yiibai.com/lua/lua_basic_syntax.html

在这里只列举一些我们常用到的一些语法说明及注意事项。

函数编写的两种方式

Lua脚本中编写自己公用的一些函数方法时,有两种写法:使用点号和使用冒号。

例如以下代码:

Demo = {

  x = 0,

  y = 0

}

function Demo**:**setPosition(x, y)

  self.x = x;

  self.y = y;

end

local who = Demo;

Demo = nil;

who**:**setPosition(1, 2);

ngx.say(who.x)

ngx.say(who.y)

执行后会输出12,而如果将上面标红加粗的冒号改为我们常用调用函数的方式点号并将方法中的self.x=x改为Demo.x=x。y也做相应的该动。该脚本就会报错。

说明:以冒号的方式调用函数时,会将调用者这个对象以隐匿的self传入。而以点号的方式调用函数时就不会传入调用者本身,一般我们编写的简直函数都是用点号即可满足业务需求。

封装自己的公用lua脚本方法

例如我们要封装一个字符串分隔方法,代码如下:

local _M = {}

_M.split = function(str, delimiter)

  if str==nil or str=='' or str==ngx.null or delimiter==nil then

    return nil

  end

  local result = {}

  for match in (str..delimiter):gmatch("(.-)"..delimiter) do

    table.insert(result, match)

  end

  return result

  end

return _M

说明:

第一行代码代表:定义lua脚本对象本身。

第二行代码代表:为对象定义一个split方法,方法有两个入参

最后一行代码代表:装返回该脚本对象(其他lua脚本引入这个脚本时,就会得到该对象,进而调用里面封装的split方法)

引用自己封装的公用lua脚本方法

在实际业务中我们会经常使用一些我们自己写的公用Lua脚本方法。例如我们要引用自己封装好的的utils.lua中split字符串分隔方法。

1、 首先openresty集成包,默认的Lua脚本扫描路径为:/usr/local/openresty/lualib

2、 如果要引用自己封装的lua脚本,有两种方法,第一种是将脚本放到该目录下(目录下的子目录也可以,通过点号进入子目录),通常我们使用第二种方法,即在脚本的头部,自己定义lua脚本的扫描路径:

package.path = ‘/usr/local/openresty/lualib/?.lua;/usr/local/openresty/nginx/lua/?.lua;’

为了方便以下章节的代码片段均以默认为配置好了package.path,代码中均省略了该语句。

多个扫描路径以分号分隔。

然后在代码正片引用我们自己编写的公用lua脚本:

local demo = require "utils"

local params=demo.split(str,delimiter)

常用的一些语法说明

空值的说明

空值有很多种情况,例如:nil,null,ngx.null,””

例如我们用到的redis脚本,获取一个键值时,如果这个键不存在,此时返回的是ngx.null,如果用nil或者null去判断,均会返回false。确认一个空值的类型到底是哪一类有两种法,一种是打印值类型:ngx.say(type(value)),另一种就是查看获取这个值的方法源码,看是怎么返回的。

Json与String的相互转换

Json2string:

local cjson = require "cjson"

local data,err = cjson.decode(json);

取值:value=data[“key”]

说明:decode的时候如果异常,异常信息会返回在err中。Lua脚本中函数会隐式返回一个额外的值,就相当于java中的throw了一个Exception。

String2json:

local cjson = require "cjson"

local data,err = cjson.encode(string)

字符串的拼接:

Lua脚本中两个字符串拼接使用的是两个点号,例如

local str=”abc”..”efg”

ngx.say(str)

运行会输出:abcefg

不等号的表达方式

Lua脚本中判断两个值不相等是使用的:~=例如

if “abc”~=”123” then

  ngx.say(“true”)

else

  ngx.say(“false”)

end

运行会输出:true

获取数组对象的长度方法

Lua脚本中获取数组对象的长度方法使用的是:#号例如

local demo = { 1 , 2 , 3 , 4 , 5 }

ngx.say(#demo)

运行会输出:5

For循环

语法格式如下:

for exp1,exp2,exp3 do 

  <执行体> 

end

说明:从exp1变化到exp2,每次变化以exp3为步长递增,并执行一次"执行体"。exp3是可选的,如果不指定,默认为1。例如:

for i = 10, 1, -1 do

  ngx.say(i)

end

运行会输出:10987654321

Redis的调用

--引用redis集群节点的配置文件

local conf = require "conf"

--引用redis集群lua脚本

local redis_cluster = require "resty.rediscluster"

--new一个redis对象

local redis,err = redis_cluster:new(conf.redis_config)

--初始化

redis:init_pipeline()

--执行redis指令,获取demokey值

redis:get("demokey")

--提交,并返回demokey的值

local res,error=redis:commit_pipeline()

说明:此时返回的res值是一个数组格式,要取得对应值的字符串,需要取res[1]

Kafka的调用

--引用kafka集群节点配置文件

local conf = require "conf"

--引用kafka集群lua脚本

local producer = require "resty.kafka.producer"

--new一个kafka对象

local p,err = producer:new(conf.broker_list)

--往topic中推送信息

local res,err=p:send(topic,key,content)

说明:推送信息时传入三个参数:

topic:推送信息所在的topic,需要在kafka服务端分配好

key:推送信息到topic的分区标识,例如,创建topic时,分配了10个分区,这里传入不的key时,不同的信息就会落在不同的分区里面,可供多个消息者并发处理,如果全部写入到一个分区,那就相当于单线程处理。

content:推送信息的内容

如果推送信息失败,err会返回相应的错误信息。

获取http请求的参数

Nginx拦截请求转发到我们的Lua脚本后,我们获得请求的相应参数,例如我们要获取客户端请求的手机号码MOBILENUM的值,代码如下:

local mobie = nil

--判断客户端http请求类型

local request_method = ngx.var.request_method

--如果是GET请求,直接获取

if request_method=="GET" then

mobile = ngx.var.arg_MOBILENUM

--如果是POST请求,同时我们定义只接受json数据格式

elseif(request_method=="POST") then

--读取POST请求的Body数据

ngx.req.read_body()

--获取Post请求的Body数据

contents=ngx.req.get_body_data()

--接口规范中定义的是json格式,如果请求的不是json则拒绝

local cjson = require "cjson"

local data,err = cjson.decode(contents);

mobile=data["MOBILENUM"]

end

说明:

1、获取GET请求参数使用的是:

ngx.var.arg_PARAM其中PARAM为参数名称

2、获取POST请求参数使用的是:

ngx.req.read_body()

local contents=ngx.req.get_body_data()

评论

发表评论

validate