像OpenResty一样使用Golang开发Web App
Jun 26 2018https://github.com/zhu327/glualor
最近在公司内网读过一篇Gopher Lua的文章, 感觉在Golang中使用Lua VM的模式跟OpenResty是一样一样的. 在Github上找了一圈net/http的到Gopher Lua的绑定, 然而并没有. 造轮子的机会来了 ^_^
gluaweb
虽然看过几本Golang的书, 也读过几个开源项目的代码, 来腾讯后还上过两门Golang的课, 但是却没有写过一个Golang的项目, 从新读过net/http标准库的文档, 再看看Gopher Lua的例子就写了gluaweb
package main
import (
"github.com/yuin/gopher-lua"
"github.com/zhu327/gluaweb"
"net/http"
)
type helloHandler struct{}
func (h *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
L := lua.NewState()
defer L.Close()
ctx := gluaweb.NewWebContext(w, r).WebContext(L)
L.SetGlobal("gluaweb", ctx)
if err := L.DoString(`
gluaweb.Write("hello world!")
`); err != nil {
panic(err)
}
}
func main() {
http.Handle("/", &helloHandler{})
http.ListenAndServe(":8080", nil)
}
使用很简单, 每个请求进来启动一个Lua VM, 然后注入gluaweb上下文, 在lua里面调用gluaweb绑定的Request或者ResponseWriter的方法即可.
到这一步基本上跟OpenResty其实就差不多了, OpenResty里Request与Response相关的方法都在ngx下, 而在gluaweb里gluaweb就相当于OpenResty的ngx.
glualor
有了最基本的Request/Response的方法还不够, 还需要Web FrameWork, 就如同Gin之于net/http. 在OpenResty那边有一个lor框架, 代码很简洁, 使用方式类似于Python的Flask. 同样是Lua写的, 只需要阅读代码, 把其中使用ngx相关的部分改写成gluaweb对应的方法即可. 包括request.lua respnse.lua cookie.lua.
lor还支持json编解码, 在OpenResty里使用的cjson, 在Github找到了gopher-json, 在main.go里面创建Lua VM时加载json module, 并且修改lor里面utils.lua json相关的代码从cjson改为gogher-json.
除了json还有template, lor使用了lua-resty-template这个库, 在Gopher Lua这边可以直接使用Golang html/template, 同样去Github上面搜索找到一个text/template到Gopher Lua的绑定, 由于text/template提供的接口与html/template是一样的所以fork到gluatemplate, 改import把text改为html. 再修改lor里面view.lua把lua-resty-template替换为gluatemplate.
main.go
package main
import (
"github.com/yuin/gopher-lua"
"github.com/zhu327/gluatemplate"
"github.com/zhu327/gluaweb"
luajson "layeh.com/gopher-json"
"net/http"
)
type helloHandler struct{}
func (h *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
L := lua.NewState()
luajson.Preload(L)
L.PreloadModule("template", gluatemplate.Loader)
defer L.Close()
ctx := gluaweb.NewWebContext(w, r).WebContext(L)
L.SetGlobal("gluaweb", ctx)
if err := L.DoFile("main.lua"); err != nil { // 加载 lua 代码
panic(err)
}
}
func main() {
http.Handle("/", &helloHandler{})
http.ListenAndServe(":8080", nil)
}
main.lua
local lor = require("lor.index")
local app = lor()
app.get("/", function(err, req, res, next) {
res:send("Hello world!")
})
app.run()
经过以上改造, 已经能在Gopher Lua上愉快的使用lor快速开发web app了, 然后ab测试一下性能, 简直不忍直视, 继续改造.
每一个请求进来都新建一个Lua VM其实是没有必要的, 可以全局创建一个VM池, 每个请求进来从池里取出一个VM运行代码即可. Gopher Lua的README上也介绍了一个goroutine安全的pool实现直接拷贝过来用就好了. 除了VM复用还需要Lua代码的复用, 而不是每一次请求都Dofile加载一次所有的代码, 这样就需要把Lua中的某个函数暴露给Golang, 在Golang中调用已有的Lua函数来处理请求, 在lor里面就是app.run这个方法, 把app声明成全局变量就可以在Golang中调用app.run了.
再ab测一下, 性能相对于原生Golang的Hello world有大概17%的损失, 相对于开发的便利性, 还是可以接受的.
有了框架还要测试下到底能不能用, lor有一个官方的TODO示例, 直接用这个跑一下试试, 最终的完整实例就是glualor
扩展
Web开发当然不只是Web Framework, OpenResty提供了全套开发库, MySQL Redis一应俱全, 但是Gopher Lua的生态还不完善, Github搜一圈并没有找到MySQL与Redis的绑定, 其实自己写也是很简单的, 有空再扩展一下^_^