看到很多简洁的 web 框架,总想自己造个轮子试试,边写边记录些比较。
参考框架
既然是造轮子,我当然选自己最熟悉的 Lua 语言了,可是已经有了基于 Openresty 的 lor 框架,所以底层我就又选用了我熟悉的 skynet 了。
明确目标: 基于 skynet 开发一个 web 框架。
web 框架都有些什么
主要功能
- 路由(router) 和路由组(routergroup)
- 中间件(middleware)
- 模板(template)
其他东西
- 能方便的 reload ,无论是开发环境还是正式环境,能不停机换上新代码是必要的功能
- 支持 REST ,这属于路由的一部分,但是也需要路由支持才能实现
- 重定向 Redirect
- 方便开发和部署
轮子取舍
为了早点出轮子,且轮子是可玩的,我对功能做了取舍。计划第一个版本只完成下面这些功能:
- 路由和路由组并支持 REST, REST 比较火也比较简洁
- 中间件,这东西虽然不是必须的,但是有了它可以很方便的写很多插件,比如拦截未认证的请求。
- 方便开发和部署,这样才会吸引别人来玩呀
为什么不实现模板呢?在现在,前后端分离开发的趋势下,模板显得没那么重要了。服务器只需要提供接口就行,模板都转移到了前端。
重定向的功能本来是计划中的,但是觉得不实现它也没什么问题,等需要的时候再补上吧。
版本规划
第一个版本定为 0.01 ,主要功能就是上面说的。后续版本现在也只是想想而已,比如目前只能在 Linux 下运行,可以尝试迁移到 Mac OS 和 Windows 下运行,因为不是必须的东西,所以就放在 TODO 计划中了。
开发路线
1. 根据参考的框架,确定框架
从使用者的角度来确定框架结构。首先是方便开发部署,所以参考了 lor 的方式,提供脚手架生成工程,工程中包含些操作脚本,比如 start.sh
, stop.sh
和 reload.sh
这些。由于选用的底层是 skynet ,所以按照 skynet 推荐的方式开发,以 skynet 为 submodule 。
预定的目录结果大概是这样的:
.
├── 3rd
├── conf
├── luaclib
├── lualib
├── Makefile
├── service
└── skynet
框架的主要代码都放在 lualib
目录,其他第三方库都放到 3rd
目录。service
是自定义的 skynet 服务目录。
框架使用 skynet 的特性很少,只用到了 2 种服务,一个主服务 main 和多个 agent 服务,web 开发逻辑都在 agent 服务上执行。
+---------+ +---------+
| agent 1 <----+ client |
+-------+ +---------+ +---------+
| main +-----+
+-------+ +---------+
| agent 2 |
+---------+
2. 首先让框架跑起来
首先就是参考 skynet/examples/simpleweb.lua
示例开启 http 服务。这里的例子把 agent 服务写在了同一个文件,把他拆开为 service/main.lua
和 lualib/wlua/agent.lua
两个文件,稍微改改就跑起来了。
3. 代码架构设计
首先从使用者的角度来设计框架,使用者的目录结构大致是这样的:
.
├── app
│ └── main.lua
├── conf
│ └── wlua.conf
├── cutlog.sh
├── kill.sh
├── reload.sh
├── start.sh
└── stop.sh
可以看出,除了那些 sh 脚本,只有两个文件是主要的。 app/main.lua
就是 agent
服务的入口,也是使用者开发 web 服务器的入口。 conf/wlua.conf
是配置文件,我想要的 app/main.lua
是这样的结构,和 lor 框架的非常类似。
local wlua = require "wlua"
local app = wlua:new()
app:get("/", function (c)
c:send("Hello wlua!")
end)
app:run()
跟 lor 还是有点区别的,这里参考了 gin 框架的样式,回调函数的参数只有 c
,是 context
对象,这个对象包含了此次客户端请求的所有信息和相关的操作。
app 这个对象也参考了 gin 框架的 engine 对象,代码结构也和 gin 框架类似。lualib
目录结构是这样的:
lualib/
├── config.lua
├── log.lua
├── middleware
│ └── logger.lua
├── r3.lua
├── util
│ ├── date.lua
│ ├── file.lua
│ ├── json.lua
│ ├── string.lua
│ └── table.lua
├── wlua
│ ├── agent.lua
│ ├── context.lua
│ ├── methods.lua
│ ├── request.lua
│ ├── response.lua
│ └── routergroup.lua
└── wlua.lua
wlua.lua
就是上面 app
对象的类的实现。wlua
类主要包含了 router
,router
最初是采用的是很简单的 APItools/router.lua ,只要代码能跑起来随时都能把它替换掉。最后发现了一个性能很强的路由库 c9s/r3 ,参考了 iresty/lua-resty-libr3 的封装,自己封装成 hanxi/lua-r3 。主要接口代码就是上面的 r3.lua
文件,之前封装的时候也写过一篇相关文章记录 https://blog.hanxi.cc/p/49 。
一个客户端请求包含什么呢?主要就者两个:
- request
- response
基本就是请求和回应了,context 把 request 和 response 组合在一起,方便使用者操作。
中间件也参考了 gin 的中间件接口和实现,也实现了一个 logger 中间件来输出 access log 。采用 use
来使用中间件,中间里采用 c:next()
函数来执行后续的 handlers
函数。
实现的 http method 就是下面这些,也基本够用了。
local M = {
GET = true,
POST = true,
PUT = true,
DELETE = true,
PATCH = true,
HEAD = true,
OPTIONS = true,
}
return M
最后
统计了下 lualib 目录下的代码,也就 820 行,还是很简洁的,开发过程中的看板可以参考下 https://github.com/hanxi/wlua/projects/1 。
目前除了精简,就只剩下不足了,比如没有实现重定向接口。而且 reload 目前还是有点粗鲁的,只是发个消息退出,如果有挂起的逻辑,会造成逻辑只跑了一半就退出了,还是有优化的余地的。
另外想学 skynet 的话,可以试试我写的这门实战课程 《Skynet 游戏服务器开发实战》 , 地址: https://www.lanqiao.cn/courses/2770 优惠邀请码: 2CZ2UA5u