lor 是一个基于 ngx_lua 的 MVC 框架,其 API 很类似于 Node 社区的著名框架 Express
lor 代码结构如下:
lor/
├── index.lua
├── lib
│ ├── application.lua
│ ├── debug.lua
│ ├── lor.lua
│ ├── methods.lua
│ ├── middleware
│ │ ├── cookie.lua
│ │ ├── init.lua
│ │ ├── params.lua
│ │ └── session.lua
│ ├── request.lua
│ ├── response.lua
│ ├── router
│ │ ├── layer.lua
│ │ ├── route.lua
│ │ └── router.lua
│ ├── utils
│ │ ├── path_to_regexp.lua
│ │ ├── path_to_regexp_lua.lua
│ │ └── utils.lua
│ ├── view.lua
│ └── wrap.lua
└── version.lua
前言
在 web 开发中,一个简化的处理流程就是:客户端发起请求,然后服务端进行处理,最后返回相关数据。不管对于哪种语言哪种框架,除去细节的处理,简化后的模型都是一样的。客户端要发起请求,首先需要一个标识,通常情况下是 URL,通过这个标识将请求发送给服务端的某个具体处理程序,在这个过程中,请求可能会经历一系列全局处理,比如验证、授权、URL 解析等,然后定位到某个处理程序进行业务处理,最后将生成的数据返回客户端,客户端将数据结合视图模版呈现出合适的样式。
至于 url 是不是指向文件是无所谓的,但最终都是要根据其定位到某个具体的处理程序,也就是 url 到 handler 有个路由映射的过程,只不过不同的框架有不同的处理方法。
1. app
使用 lor 创建一个简单的应用
local lor = require("lor.index")
local app = lor()
app:get("/", function(req, res, next)
res:send("hello world!")
end)
app:run()
通过查看源码,可以发现 lor 其实是一个 wrap 对象,worker 内唯一。lor()
会调用自身的元方法来创建一个 app 实例(每请求)。相关源码如下:
local Application = require("lor.lib.application")
local Wrap = require("lor.lib.wrap")
LOR_FRAMEWORK_DEBUG = false
local createApplication = function(options)
if options and options.debug and type(options.debug) == 'boolean' then
LOR_FRAMEWORK_DEBUG = options.debug
end
local app = Application:new()
app:init(options)
return app
end
local lor = Wrap:new(createApplication, Router, Route, Request, Response)
return lor
可以看出,事实上 lor 是将请求交给了 app,通过 app:run()
来处理了(其实最终是 app:handle()
)。
对于 lor 每个请求都会生成一个 app 实例,一个 app 实例有且仅有一个 router 对象,在这个对象里有一个 stack 数组,里面保存了所有 layer(中间件) 相关的信息
注意: 其实未必是一个 Router, 这里指的是一个主 Router ,其下面可能会挂载很多子 router(路由组)和普通的 route
2. 中间件
一个 lor 的应用从本质来说就是一系列的中间件的调用。其实,一个中间件,就是一个函数。通常情况下,一个中间件函数(通常都是匿名函数)的形式如下:
function (req, res, next) {
-- do something
}
如果是错误处理的中间件,形式如下:
function (err, req, res, next) {
-- do something
}
参数中,req 和 res 分别表示请求的 request 和 response ,next 本身也是一个函数,调用 next()
就会继续执行下一个中间件。其实,请求的处理过程就是依次经过各个中间件。
中间件大体上可以分为两种:普通中间件和路由中间件。注册普通中间件,通常是通过 app.use()
方法;而注册路由中间件,通常是通过 app.METHOD()
方法。例如:
app:use("/user", function(req, res, next)
count = 1
next()
end)
app:get("/user/123", function(req, res, next)
count = 2
end)
以上两者主要区别在于:
- 前者匹配所有以 /user 开始的路径,而后者会精确匹配 /user/123 路径;
- 前者对于请求的方法没有限制,而后者只能处理方法为GET的请求。
在了解请求处理的详细过程之前,需要先来了解 Router。
3. router
简单来说,Router 就是一个中间件的容器。事实上,Router 是 lor 一个非常核心的东西。App 的很多 API,例如:app:use, app:handle 等,都是对 Router 的一个简单封装。
Router 对象有一个 stack 属性,是一个数组,里面存放了所有的中间件。当调用 app:use
来注册中间件的时候,实际上是执行了 router:use
,从而向 router.stack 数组中添加中间件。router.stack 中的每一项是一个 layer 对象,它是对中间件函数的一个封装。添加中间件的部分相关源码如下:
function Router:use(path, fn, fn_args_length)
local layer
if type(fn) == "function" then -- fn is a function
layer = Layer:new(path, {
is_end = false,
is_start = true
}, fn, fn_args_length)
else -- fn is a group router
layer = Layer:new(path, {
is_end = false,
is_start = true
}, fn.call(fn), fn_args_length)
local group_router_stack = fn.stack
if group_router_stack and not fn.is_repatterned then
fn.is_repatterned = true -- fixbug: fn.is_repatternd to remember, avoid 404 error when "lua_code_cache on"
for i, v in ipairs(group_router_stack) do
v.pattern = utils.clear_slash("^/" .. path .. v.pattern)
end
end
debug("router.lua#use-inner now the group router(" .. fn.name .. ") stack is:")
debug(function()
for i, v in ipairs(fn.stack) do
print(i, v)
end
end)
debug("router.lua#use-inner now the group router(" .. fn.name .. ") stack is------\n")
end
tinsert(self.stack, layer)
debug("router.lua#use now the router(" .. self.name .. ") stack is:")
debug(function()
for i, v in ipairs(self.stack) do
print(i, v)
end
end)
debug("router.lua#use now the router(" .. self.name .. ") stack is------\n")
return self
end
上面是添加普通的中间件和路由组,大概如此。不过,对于路由中间件,就稍微复杂了些。在此之前,先看下添加路由中间件的方法:
-- app:get -> router:app_route -> route:get
app:get("/index", function(req, res, next)
res:send("hello world!")
end)
-- application.lua
function App:init_method()
for http_method, _ in pairs(supported_http_methods) do
self[http_method] = function(self, path, fn)
debug("\napp:" .. http_method, path, "start init##############################")
local route = self.router:app_route(path)
route[http_method](route, fn) -- like route:get(fn)
debug("app:" .. http_method, path, "end init################################\n")
return self
end
end
end
可以看到,添加路由中间件是通过 router.app_route()
来创建一条新的路由,然后调用 route:http_method(fn)
来注册相关的处理函数。
Route 可以简单理解为存放路由处理函数的容器,它也有一个 stack 属性,为一个数组,其中的每一项也是一个 layer 对象,是对路由处理函数的包装。下面来看当执行 router.app_route()
的时候发生了什么:
function Router:app_route(path)
local route = Route:new(path)
local layer = Layer:new(path, {
is_end = true,
is_start = true
}, route, 3) -- important: a magick to supply route:dispatch
layer.route = route
tinsert(self.stack, layer)
debug("router.lua#route now the router(" .. self.name .. ") stack is:")
debug(function()
for i, v in ipairs(self.stack) do
print(i, v)
end
end)
debug("router.lua#route now the router(" .. self.name .. ") stack is++++++\n")
return route
end
也就是说,当调用 router.app_route()
的时候,实际上是新建了一个 layer 放在 router.stack 中,并设置 layer.route 为新建的 route 对象,最后把 route 对象 return 出来。
下面来看 route:http_method(fn)
的时候发生了什么:
function Route:initMethod()
for http_method, _ in pairs(supported_http_methods) do
self[http_method] = function(self, fn)
local layer = Layer:new("/", {
is_end = true
}, fn, 3)
layer.method = http_method
self.methods[http_method] = true
tinsert(self.stack, layer)
debug("route.lua# now the route(" .. self.name .. ") stack is:")
debug(function()
for i, v in ipairs(self.stack) do
print(i, v)
end
end)
debug("route.lua# now the route(" .. self.name .. ") stack is~~~~~~~~~~~~\n")
end
end
end
即:当调用 route:http_method(fn)
的时候,新建了一个 layer 放在了 route.stack 中。通过上面分析发现,router 其实是一个二维的结构。
4. 请求处理过程
前面所提到的,无论是添加中间件,还是注册路由,都是应用的构建过程。当应用构建好了之后,客户端发起请求,这个时候,应用就开始使用前面的中间件和参数处理函数,来处理客户端的请求。
前面提到,所有的请求,都是由 app.handle()
来处理的,通过看源码,可以发现,其实 app.handle()
是调用了 router.handle()
。
当请求到来时,经过中间件的顺序大致如下所示:
router.stack 中存的是一个个的 layer 对象,用来管理中间件。如果 layer 对象表示的是一个路由中间件,则其 route 属性会指向一个 route 对象,而 route.stack 中存放的也是一个个的 layer 对象,用来管理路由处理函数。
因此,当一个请求到来的时候,会依次通过 router.stack 中的 layer 对象,如果遇到路由中间件,则会依次通过 route.stack 中的 layer 对象。
对于 router.stack 中的每个 layer 对象:
- 会先判断是否匹配请求路径,如果不匹配,则跳过,继续下一个。
- 在路径匹配的情况下,如果是非路由中间件,则执行该中间件函数;
- 如果是路由中间件,则继续判断该中间件的路由对象能够处理请求的 HTTP 方法,如果不能够处理,则跳过继续下一个,如果能够处理则对 route.stack 中的 layer 对象(与请求的 HTTP 方法匹配的)依次执行。示例图如下(路由组):
5. lor 不足
app
的构建过程不够lazy
,也就是每次请求进来都需要不断的构建layer
,即使是不匹配的path
- 错误处理插件需要不断遍历
layer
,虽然不用执行fn
提升性能的一个小
trick
:
Before:
-- main.lua
local lor = require("lor.index")
local app = lor()
app:get("/", function(req, res, next)
res:send("hello world!")
end)
app:run()
After:
-- main.lua
local app = require("app.app")
app:run()
-- app.lua
local lor = require("lor.index")
local app = lor()
app:get("/", function(req, res, next)
res:send("hello world!")
end)
return app
性能比较: