作者:糖果

LUA代码:

list = {1,5,3,2,9,3,6}
len=#list

for i=1,len 
  max = list[i]
  for j=i+1, len do  
    if list[j]>max then 
      tmp=list[j] 
      list[j]=max
      list[i]=tmp
      max=tmp

for item in *list
  print(item)

MoonScript代码

local list = { 
  1,  
  5,  
  3,  
  2,  
  9,  
  3,  
  6
}
local len = #list
for i = 1, len do
  local max = list[i]
  for j = i + 1, len do
    if list[j] > max then
      local tmp = list[j]
      list[j] = max 
      list[i] = tmp 
      max = tmp 
    end 
  end 
end
for _index_0 = 1, #list do
  local item = list[_index_0]
  print(item)
end

作者:糖果

Lapis的util库有对cjson封装,而我们想更直接的调用CJSON的方法,而不想依赖的封装。

我们首先实现一个MoonScript写文件的代码:

写文件:


write_data = (var, rule)->
    path = "/home/xxx"  
    file = io.open(path,"aw")
    if file==nil then
        return

    ret = file\write(rule)
    file\close()
    return(t)

访问接口:

    restyhttp = require "resty.http"
    httpc = restyhttp.new()
    res, err = httpc\request_uri("http://0.0.0.0/getjson")
    jsonbody = ""
    if res.status == ngx.HTTP_OK 
      jsonbody = res.body
    else 
      jsonbody = nil 

对JSON数据DECODE:

    write_data(0, jsonbody)
    t = json.decode(jsonbody)
    tjson = json.decode(t['message'])
    ngx.say(util.serialise_value(tjson))
    ""

作者:糖果

在OpenResty中发起HTTP请求,一般情况下,有两种方式: 1.通过内部Proxy。 2.使用RESTY-HTTP库发起访问。

Lapis使用的是interal proxy,之前文章有提到,下面提到的是RESTY-HTTP的MoonScript调用 实现。

RESTY-HTTP安装

实际RESTY-HTTP的主要实现就是两个lua文件, http_headers.lua和http.lua这两个文件。 将文件复制到/usr/local/openresty/lualib/resty下即可使用,再引用http.lua时注意一下 的是Lapis也有一个同名文件,需要注意一下冲突。

MoonScript代码:

    http = require "resty.http"
    httpc = http.new()
    res, err = httpc\request_uri("http://www.baidu.com")
    if res.status == ngx.HTTP_OK 
      return res.body
    else 
      return "nil"   

LUA代码:

    ["/testcase"] = function(self)
      local restyhttp = require("resty.http")
      local httpc = restyhttp.new()
      local res, err = httpc:request_uri("http://www.baidu.com")
      if res.status == ngx.HTTP_OK then
        return res.body
      else
        return "nil"
      end
    end

MoonScript和LUA代码几乎没太大区别,因为request_uri请求中使用的是域名,所以需要 修改conf文件。

nginx.conf配置

    location / {
            resolver 8.8.8.8;
    }            

RESTY-HTTP与CJSON不同,并没有涉及到任何so库的生成,http_headers.lua和http.lua这 两个文件也是在Makefile来实现的,使用的是install命令-d参数,相当于在cp过程中,如果目标位置没 有相应的文件夹就创建一个文件夹。

Makefile

OPENRESTY_PREFIX=/usr/local/openresty

PREFIX ?=          /usr/local
LUA_INCLUDE_DIR ?= $(PREFIX)/include
LUA_LIB_DIR ?=     $(PREFIX)/lib/lua/$(LUA_VERSION)
INSTALL ?= install
TEST_FILE ?= t

.PHONY: all test install

all: ;

install: all
        $(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR)/resty
        $(INSTALL) lib/resty/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty/


作者:糖果

LazyTable源码

local ngx_req = {
      headers = function()
          return "testcase"
      end,
      method = function()
          return "GET"
      end,
}


local lazy_tbl
lazy_tbl = function(tbl, index)
  return setmetatable(tbl, {
    __index = function(self, key)
      for k,v in pairs(index) do
          print(k, v)
      end
      --print(key)
      for k,v in pairs(self) do
          print(k, v)
      end
      local fn = index[key]
      if fn then
        do
          --此处fn(self)和fn(),传入self形参与否的效果是一样,table中的匿名函数
          --定义是没有参数的,这个形参是moonscript翻译后加上去的。
          local res = fn(self)
          --local res = fn(self)        
          local res = fn()
          self[key] = res
          --实际把return res去掉也不会影响这个程序的运行结果, 在此函数中
          --从了这个return语句再也没有return调用了,而设定工作在self[key] = res这句就已经完成。
          return res
        end
      end
    end
  })
end


local build_request
build_request = function(unlazy)
  if unlazy == nil then
    unlazy = false
    --unlazy = true
  end
  do
    local t = lazy_tbl({ }, ngx_req)
    if unlazy then
      for k in pairs(ngx_req) do
        --这里遍历的ngx_req,但是调用的函数是t的。
        local _ = t[k]
      end
    end
    return t
  end
end


req = build_request("unlazy")


for k,v in pairs(req) do
    print(k, v)
end

重置setmetatable

在一个匿名函数中,return setmetatable,通过对函数形参传入的table的变量的__index属 性进行统一修改,而新设定的__index对应函数的形参self和key,分别对应新table的本身, 和table对应的key。

 __index = function(self, key)
  lazy_tbl({ }, ngx_req)

self:是ngx_req的引用。 key:是ngx_req的key。

如果发现,ngx_req的元素类型是function就执行下,然后,把返回结果(字符串) 替换原value值。

核心原理

lazy table实现的核心部分是,是在return setmetatable做table复制时,并统一的设定 新table的__index属性, 然后在遍历我们要批量设定的table时,得用table的__index对 应函数中的设置,修改要修改table的成员变量值,把table中,对应key的值是function 的元素,改成key的值等于,这个key对应匿名函数的返回值。

修改table元素的是由setmetatable指定的__index方法来完成。而遍历循环执行table中的 所有匿名函数,由一次外层的循环来完成。

moonscript的lua翻译特征

do end 结构是为了让local型的局部变量,在 do end 结构外不可见。

 do 
    local tmp = 1
 end 
 print(tmp)

结果是:nil

 do 
    tmp = 1
 end 
 print(tmp)

结果是:1

下面的代码有明显的moonscript翻译成lua的特征:

local test 
test = function()
    do  
        local tmp = "do end"
        return tmp 
    end 
    return "ret"
end

ret = test()
print(ret)

简化后的LazyTable代码

local ngx = { 
    url = function() 
        return "url"
    end,
    method = function()
        return "method"
    end,
}

local lazy

lazy = function(tbl, index)
    return setmetatable (tbl, {
        __index = function(self, key)
            print(key)
        end 
    })  
end


local build_request
build_request = function(unlazy)
        ret = lazy({}, ngx)
        for k in pairs(ngx) do
             local _ = ret[k]
        end
end

build_request("")

lazy的核心是通过lazy函数,重新设置table的__index对应函数 ,在函数中调用元素值是function类型的函数,用函数返回结果修 改当前元素的value值。

通过LazyTable可把Table表的声明和元素值的动态设定在一个 封装调用周期内完成。

应用场景

LazyTable在应用在Lapis的获取nginx变量的一个功能,我们把这个功能 移到HiLua演示框架里,看LazyTable如何在实际应用场景应用的。

local ngx_request = {
  headers = function()
    return ngx.req.get_headers()
  end,
  cmd_meth = function()
    return ngx.var.request_method
  end,
  cmd_url = function()
    return ngx.var.request_uri
  end,
}

local lazy_tbl
lazy_tbl = function(tbl, index)
  return setmetatable(tbl, {
    __index = function(self, key)
      local fn = index[key]
      if fn then
        do  
          local res = fn(self)
          self[key] = res 
          return res 
        end 
      end 
    end 
  })  
end

return ngx_request

Moonscript是有OO的类的组织结构,但是用Moonscript写的Lapis框架,有的一些文件并没有 按类的形式组织,比如上面这个截取的代码,最后返回的就是一个ngx_request的table,通过 引用table中的函数类型元素进行调用,即可返回你想要的数据,这个类似之前lapis ES的写法 一个孙数的调用过程,可以整个组织成一个table声明的过程,在其过程中就完成了各种行为的 调用,看起来很整洁,调用时序隐含在声明里,而不是传统的若干个函数过程顺序调用的方式。

在一个数据结构中定义若干函数的调用,要是和路由器比较的话,路由器需要正则匹配判断 而这个是顺序执行。

在HiLua中的调用形式如下:

local req = require "nginx"

app:get("/hilua", function(request,id)
    ngx.say(type(req.cmd_meth))
end

nginx中ngx_request取得HTTP请求阶段的各种变量,这个数据结构在HiLua进行路由匹配处理 从这个数据结构中,取得相关变量。

local req = require "nginx"
function Route:run(router)
        local url = req.cmd_url()
        local method = req.cmd_meth()
end

LazyTable本身是一种设计实现的思路,可以用Moonscript实现,也可以用Lua实现,也可以 C/CPP实现。

作者:糖果

Lapis使用JSON解析的代层库就是CJSON。

遍历Table变量的所有元素。

util.moon

json_encodable = (obj, seen={}) ->
  switch type obj
    when "table"
      unless seen[obj]
        seen[obj] = true
        { k, json_encodable(v) for k,v in pairs(obj) when type(k) == "string" or type(k) == "number" }
    when "function", "userdata", "thread"
      nil
    else
      obj

to_json = (obj) -> json.encode json_encodable obj
from_json = (obj) -> json.decode obj

json_encodable递归调用检查形参,seen={} 在形参列表里的应用。 检查出所有字符中含有的JSON数据,放入Table返回。

util.lua

json_encodable = function(obj, seen)
  if seen == nil then
    seen = { }
  end
  local _exp_0 = type(obj)
  if "table" == _exp_0 then
    if not (seen[obj]) then
      seen[obj] = true
      local _tbl_0 = { }
      for k, v in pairs(obj) do
        if type(k) == "string" or type(k) == "number" then
          _tbl_0[k] = json_encodable(v)
        end
      end
      return _tbl_0
    end
  elseif "function" == _exp_0 or "userdata" == _exp_0 or "thread" == _exp_0 then
    return nil
  else
    return obj
  end
end

to_json = function(obj)
  return json.encode(json_encodable(obj))
end

from_json = function(obj)
  return json.decode(obj)
end

Lua版因为没有像MoonScript支持When语句,被翻译成了很多的if elseif end语句。

Lapis调用的JSON基础就是lua-cjson这个库,这个库同样有一个类似的递归调用就是 :serialise_value。


local function is_array(table)
    local max = 0 
    local count = 0 
    for k, v in pairs(table) do
        if type(k) == "number" then
            if k > max then max = k end 
            count = count + 1 
        else
            return -1
        end 
    end 
    if max > count * 2 then
        return -1
    end 

    return max 
end
local serialise_value

local function serialise_table(value, indent, depth)
    local spacing, spacing2, indent2
    if indent then
        spacing = "\n" .. indent
        spacing2 = spacing .. "  "
        indent2 = indent .. "  "
    else
        spacing, spacing2, indent2 = " ", " ", false
    end 
    depth = depth + 1 
    if depth > 50 then
        return "Cannot serialise any further: too many nested tables"
    end

    local max = is_array(value)

    local comma = false
    local fragment = { "{" .. spacing2 }

    if max > 0 then
        -- Serialise array
        for i = 1, max do
            if comma then
                table.insert(fragment, "," .. spacing2)
            end
            table.insert(fragment, serialise_value(value[i], indent2, depth))
            comma = true
        end
    elseif max < 0 then
        -- Serialise table
        for k, v in pairs(value) do
            if comma then
                table.insert(fragment, "," .. spacing2)
            end
            table.insert(fragment,
                ("[%s] = %s"):format(serialise_value(k, indent2, depth),
                                     serialise_value(v, indent2, depth)))
            comma = true
        end
    end
    table.insert(fragment, spacing .. "}")

    return table.concat(fragment)
end



function serialise_value(value, indent, depth)
    if indent == nil then indent = "" end
    if depth == nil then depth = 0 end

    if value == json.null then
        return "json.null"
    elseif type(value) == "string" then
        return ("%q"):format(value)
    elseif type(value) == "nil" or type(value) == "number" or
           type(value) == "boolean" then
        return tostring(value)
    elseif type(value) == "table" then
        return serialise_table(value, indent, depth)
    else
        return "\"<" .. type(value) .. ">\""
    end
end

调用序列化,如果table变量里的value还是table就递归的调用serialise_value函数自己。

meta_info = { 
    key = "test key:",
    values = { 
        k = "key",
        v = "value"
    },  
    testcase = "null"
}

print(serialise_value(meta_info))

以上函数是直接从CJSON中提取出来的,可以遍历任意的table,相关于pprint这种功能。

打印结果如下:

{
  ["testcase"] = json.null,
  ["key"] = "test key:",
  ["values"] = {
    ["k"] = "key",
    ["v"] = "value"
  }
}

cjson.null与nil、NULL是否等价。

下面是@hambut老师的测试代码:

local cjson = require "cjson"

local s = [[{"key":null,"key1":"value"}]]
local sd = cjson.decode(s)
sd.key2 = "value2"

ngx.say(cjson.encode(sd))

打印结果如下:

{"key1":"value","key":null,"key2":"value2"}

再做一个实验, 我们直接写一个so库,同句函数cjson_new,只返回一个table结构数据。

extern int cjson_new(lua_State* L); 

static luaL_Reg libtangguo[] = { 
    {"cjson_new", cjson_new},
    {NULL, NULL}
};


int cjson_new(lua_State* L) {
    lua_newtable(L);
    /* Set cjson.null */
    lua_pushlightuserdata(L, NULL);
    lua_setfield(L, -2, "null");
    return 1;
}

cjson.null 就是.so库返回的一个table变量,中的一个key名为 “null”, value是userdata(nil) 的userdata类型的table元素,lightuserdata不归GC管理,就是一个的指针,一般就用于和 其它的lightsuerdata比较, 而这个其它的lightuserdata变量,一般也都是C产生的,是产生 lightuserdata之间比,不是C的lightuserdata和lua的lightuserdata比法。

lightuserdata和userdata也不一样, lightuserdata是指针,userdata是buffer。

local cjson_new = package.loadlib("libtangguo.so", "cjson_new")
local cjson = cjson_new()

for k,v in pairs(cjson) do
    print(k, v, type(v))
end

print(cjson.null)
tmp = cjson.null
print(tmp, type(tmp))


if cjson.null == nil then
    print("OK")
end

if cjson.null == "null" then
    print("OK")
end


if cjson.null == tmp then
    print("OK")
end

cjson.null不是nil, 更不是”null”。数据在内存空间的存储形式是不一样的,但表示的现实意义是一样的,表示“啥也没有”。

为了进一步说明是这样的,我们直接看一下pushlightusredata的C源码:

lapi.c

/*
** Union of all Lua values
*/
typedef union Value {
  GCObject *gc;    /* collectable objects */
  void *p;         /* light userdata */
  int b;           /* booleans */
  lua_CFunction f; /* light C functions */
  lua_Integer i;   /* integer numbers */
  lua_Number n;    /* float numbers */
} Value;


LUA_API void lua_pushlightuserdata (lua_State *L, void *p) {
  lua_lock(L);
  setpvalue(L->top, p);
  api_incr_top(L);
  lua_unlock(L);
}

cjson.so

    lua_newtable(L);
    lua_pushlightuserdata(L, NULL);
    lua_setfield(L, -2, "null");

这样在实际调用时, setpvalue(L->top, p); 相当于 void *p = NULL, 最后是被封装到table变量里返回的。