How to use Feeling Responsive

Blues框架

The documentation is a work in progress...

编辑

此处为开源. 帮助改进.

1.Blues的Request库的使用

在说到http-src这个插件时,说过要用blues框架的nginx库来取得用户请求的数据,现在我 们就看看这两个库的的实现的,简要的说明,因为在介绍LazyTable时,专门说了nginx.lua 的实现,而request库是直接调用blues的nginx来取得用户数据,所以我们先看blues的request 是如何使用的,httpsrc插件的使用方法一样。

request库的代码实现很简单,从Blues使用Request库来说明。

在blues框架中创建一个GET匿名方法,用于测试request功能,代码如下:

app:get("/blockip", function(request,id)
    ngx.say(request.params['cmd_url'])
end)

启动Openresty,测试一下这个路由过程:

hi start
curl 0.0.0.0/blockip

显示结果:

/blockip

调用时序是:app->request->nginx

匿名函数很简单,我们观察一下request如何让自己的params结构中添充数据的,这数据的 取得就是来至于nginx的lazytable实现,代码如下:

local params = require "nginx"
local Request = {}

function Request.getInstance()

        local name = "request"
        local instance = { 
                            getName = function() end 
                        }   

        instance.params = params
        setmetatable(instance, { __index = self,
                                 __call = function()
                                        end 
                                 })  
        return instance
end

return Request

可能说Request简直就是nginx一层简单的封装,最主要的一句就是:

instance.params = params

就是这句话调用LazyTable实现了数据的初始化,让request.params中有了用户请求数据。

关于nginx.lua库实现不展开说,之前有说明:使用LazyTable在Openresty中取得用户请求信息

nginx.lua的如何实现不展开,但是目前nginx.lua支持返回那些用户请求数据可查看数据 结构,如下:

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,
  body = function()
    ngx.req.read_body()
    local data = ngx.req.get_body_data()
    return data
  end,
  content_type = function()
    local content_type = ngx.header['content-type']
    return content_type
  end
}

总结,以下:

params['header']
params['cmd_meth']
params['cmd_url']
params['body']
params['content_type']

使用nginx.lua库的例子:

local params = require "nginx"
url = params['cmd_url']

我们直接用引用后,取值即可,在httpsrc插件中也是这么用:

function httpsrc_plugin.action(self, stream)
    local params = require "nginx"
    self.sink['request']['url'] = params['cmd_url']
end

以上就是如何在httpsrc插件中使用blues的nginx.lua库取得用户请求数据。

2.Blues框架引入Pipeline模式做插件系统


Blues框架基本是作为Wario项目的一个公共库存在, 代码量很少,我们试着在Blues中也引入 Pipline模式的插件管理系统,这样意着可以控制框架各种功能的开关。

之前的代码比较啰嗦,我们重构了代码:

blues.lua

Blues类,就是过去的Application类,我们把匿名函函数的形参变了, 变成了app数据结结构本身作为参数。

local Route = require("proute")
local Request = require("request")

local Blues = {}

Blues.blues_id = 1 

function Blues.new(self, lib)
        app.app_id = 1 
        app.router = Route:getInstance()
        app.req = Request:getInstance()

        app.get = function(self, url, callback)
                app:router(url, callback, "GET")
        end 

        app.post = function(self, url, callback)
                app:router(url, callback, "POST")
        end 

        app.run = function(self)
                fun = Route:run(app.router)
                if fun then
                    local ret = fun(app)
                    local rtype = type(ret)
                    if rtype == "table"  then
                        json = require "cjson"
                        ngx.header['Content-Type'] = 'application/json; charset=utf-8'
                        ngx.say(json.encode(ret))
                    end 
                    if rtype == "string"  then
                        ngx.header['Content-Type'] = 'text/plain; charset=UTF-8'
                        ngx.say(ret)
                    end 
                end 
        end 

        return app 
end

return Blues:new {
    pipeline='Pipeline System.'
}


Pipeline组件的位置就是代码,如下:

return Blues:new {
    pipeline='Pipeline System.'
}

这就是未来的放插件的位置:

proute.lua

简单路由基上没变动,除了给函数加了self形参。

local tinsert = table.insert
local req = require "nginx"

local Route =  {}

function Route.getInstance(self)
        local instance = {}
        instance.map = {
            get = {},   --get
            post = {}   --post
        }   

        instance.idea = 1 

        local router = {}
        function router.register(self, app, url, callback, method)
                if method == "GET" then
                    tinsert(self.map.get, {url, callback})
                elseif method == "POST" then
                    tinsert(self.map.post, {url, callback})
                end 
        end 

        router.__call = router.register
        setmetatable(instance, router)
        return instance
end

function Route.run(self, router)

        local url = req.cmd_url
        local method = req.cmd_meth

        if method == "POST" then
                for k,v in pairs(router.map.post) do
                        if router.map.post[k][1] == url then
                                return router.map.post[k][2]
                        end 
                end 
        end 

        if method == "GET" then
                for k,v in pairs(router.map.get) do
                        local match = string.find(url, router.map.get[k][1])
                        --if router.map.get[k][1] == url then
                        if match then
                                return router.map.get[k][2]
                        end
                end
        end

end

return Route

然后,把Wario的app.lua里的代码改成最新的形式:

local app = require "blues"

app:get("/rule", function(self)
    ngx.say(self.app_id)
    self.app_id = 6 
    ngx.say(self.app_id)
end)

return app

这样匿名函数可以取得所有加入到app中的库的引用。

function router.register(self, app, url, callback, method)
    if method == "GET" then
        tinsert(self.map.get, {url, callback})
    elseif method == "POST" then
        tinsert(self.map.post, {url, callback})
    end 
end 

router.register()函数的第二个参数,就是调用类Blues的引用,可以通过这个参数,取得所有Blues类的数据,所以 在Route类里不用引入nginx这个库了,在最外层的Blues类中定义这个成员就可以了。

local req = require "nginx"

如果还可以改造的话,就是这样,Blues所有非共通库,都用pipeline组织:

function Blues.new(self, lib)
end

return Blues:new {
    pipeline='Pipeline System.',
    
    run=function(self) 
        local src = { 
            metadata= { 
                data="http data",
                request = { 
                    uri="http://www.candylab.net"
                }   
            }   
        }   
        for k,v in pairs(self.element_list) do
            v:init()
            v:push(src)
            local src, sink = v:match('params')
            if type(sink) == "table" then
                self:output(sink, 0)
            end 
            src = sink
        end 
    end 

}

在new出blues时,以参数传入。可以把 new {} 插号中的位置,当成类声明,直接加入接口与属性数据,这个new出来的 类就是pipeline主类。

不推荐这种写法,看着就乱,独立到文件里更合适,但的确是可以这么写。

3.快速解析JSON

之前在使用别的语言框架时,发现路由处理json时,引用很多东西,觉得使用起来太啰嗦, 框架改的意思也不大,自己在重写一个小的框架时,就把这种实现简化了。

    app.json = function(self)
        local jsondata= self.request.params.body
        local t = self.bjson.decode(jsondata)
        return t    
    end 

把在匿名函数的调用封装到一个函数里。

local Blues = {}

Blues.blues_id = 1

function Blues.new(self, lib)

        local app = {}
        app.app_id = 1

        app.bjson = lib.bjson
        app.request = lib.request
        app.router = lib.router
        app.router.req = lib.nginx

        app.get = function(self, url, callback)
            app:router(url, callback, "GET")
        end

        app.post = function(self, url, callback)
            app:router(url, callback, "POST")
        end

        app.run = function(self)
            fun = app.router:finder()
            if fun then
                local content = fun(app)
                app:render(content)
            end 
        end 

        app.json = function(self)
            local jsondata= self.request.params.body
            local t = self.bjson.decode(jsondata)
            return t    
        end 
    
        return app
end

return Blues:new  {
    nginx = require("nginx"),
    bjson = require("utils.bjson"),
    request = require("request"):getInstance(),
    router = require("route"):getInstance()
}

Wario在调用时的形参也发生了变化:

local bjson = require "utils.bjson"
local app = require "blues"

app:get("/json", function(self)
    local ret = self.request.params.body
    local t = bjson.decode(ret)
    return t    
end)

app:get("/getjson", function(self)
    return self:json()
end)

return app 

4.POST处理方法

之前Blues的路由简单的都是用于测试GET方法的,激活一下Blues的Post方法,处理Post 匿名函数请求。

便利查找POST匿名函数表的方式和GET的一样,简单粗暴。

function Route.post(self)
    local url = self.req.cmd_url
    for k,v in pairs(self.map.post) do
        if self.map.post[k][1] == url then
            return self.map.post[k][2]
        end 
    end 
end

之前GET路由时序很简单,看一眼就明白了,看一POST函数处理和BLUES里和GET有什么区别:

curl调用

curl -X POST www.candylab.net/mypost -d '123'

代码在形式上真的和get没啥区别,就看self中关于post的相关的数据取得,代码如下:

app:post("/mypost", function(self)
    ngx.say("post")
end)

然后,我的就要处理如何在nginx.lua中解析post的请求数据。

我们尝试不在框架中解析POST函数,只在匿名函数中直接取得POST请求数据。

ngx.req.read_body()
local req = ngx.req.get_post_args()
ngx.say(req["key"])