From 350ec51345714855c50d295ca9b334a063db7a48 Mon Sep 17 00:00:00 2001 From: dingjun Date: Fri, 23 Aug 2019 18:16:05 +0800 Subject: [PATCH] add lua support --- api.lua | 53 +++++++++++++++++++++++++++++ compile.go | 82 +++++++++++++++++++++++++++++++++++++++++++++ conf.go | 4 +++ config.yaml | 29 ++++++++++++++++ config_example.yaml | 3 ++ go.mod | 11 ++++-- go.sum | 46 +++++++++++++++++-------- log.go | 17 ++++++++++ lua_handler.go | 49 +++++++++++++++++++++++++++ pool.go | 68 +++++++++++++++++++++++++++++++++++++ server.go | 7 ++++ 11 files changed, 352 insertions(+), 17 deletions(-) create mode 100644 api.lua create mode 100644 compile.go create mode 100644 config.yaml create mode 100644 log.go create mode 100644 lua_handler.go create mode 100644 pool.go diff --git a/api.lua b/api.lua new file mode 100644 index 0000000..6ab4e25 --- /dev/null +++ b/api.lua @@ -0,0 +1,53 @@ +local http = require "http" +local json = require "json" +local mysql = require "mysql" +local redis = require "redis" + +infolog(string.format("%s %s %s", request:GetIP(),request.Method,request.RequestURI)) + +-- response:Write(string.format("hello %s\n",request:GetIP())) +if request.URL.Path == "/api/ip" then + local resp = http.get("http://httpbin.org/ip") + local d = json.decode(resp.body) + print(d) + infolog(string.format("ip %s",d.origin)) + local hdr = response:Header() + hdr:Set("content-type","application/json") + response:Write(resp.body) + return +end + +if request.URL.Path == "/api/user" then + local c = mysql.new() + local ok, err = c:connect({host="127.0.0.1",user="root",password="112233",database="game"}) + if not ok then + error(err) + return + end + + local res, err = c:query("select * from `user` limit 3") + if err ~= nil then + error(err) + return + end + for k,v in pairs(res) do + print(k,v) + for k1,v1 in pairs(v) do + print(k1,v1) + end + end + local hdr = response:Header() + hdr:Set("content-type","application/json") + response:Write(json.encode(res)) + c:close() + return +end + +if request.URL.Path == "/api/user2" then + local c = redis.new({host="127.0.0.1"}) + local keys = c:Do("keys", "wallet*"):Val() + for k, v in keys() do + response:Write(string.format("%s %s\n", k, v)) + end + c:Close() +end \ No newline at end of file diff --git a/compile.go b/compile.go new file mode 100644 index 0000000..0af58ba --- /dev/null +++ b/compile.go @@ -0,0 +1,82 @@ +package main + +import ( + "bufio" + "os" + "sync" + + "github.com/fangdingjun/go-log" + lua "github.com/yuin/gopher-lua" + "github.com/yuin/gopher-lua/parse" +) + +// from https://github.com/yuin/gophoer-lua + +// CompileLua reads the passed lua file from disk and compiles it. +func CompileLua(filePath string) (*lua.FunctionProto, error) { + file, err := os.Open(filePath) + defer file.Close() + if err != nil { + return nil, err + } + reader := bufio.NewReader(file) + chunk, err := parse.Parse(reader, filePath) + if err != nil { + return nil, err + } + proto, err := lua.Compile(chunk, filePath) + if err != nil { + return nil, err + } + return proto, nil +} + +// DoCompiledFile takes a FunctionProto, as returned by CompileLua, and runs it in the LState. It is equivalent +// to calling DoFile on the LState with the original source file. +func DoCompiledFile(L *lua.LState, proto *lua.FunctionProto) error { + lfunc := L.NewFunctionFromProto(proto) + L.Push(lfunc) + return L.PCall(0, lua.MultRet, nil) +} + +type filecache struct { + filepath string + proto *lua.FunctionProto + lastMod int64 +} + +var cache = map[string]*filecache{} +var mu = new(sync.Mutex) + +func runFile(vm *lua.LState, filepath string) error { + mu.Lock() + c, ok := cache[filepath] + if !ok { + c = &filecache{ + filepath: filepath, + lastMod: 0, + } + cache[filepath] = c + } + + fi, err := os.Stat(filepath) + if err != nil { + mu.Unlock() + return err + } + + t := fi.ModTime().Unix() + if t > c.lastMod { + log.Debugf("file %s changed, reload", filepath) + proto, err := CompileLua(filepath) + if err != nil { + mu.Unlock() + return err + } + c.proto = proto + c.lastMod = t + } + mu.Unlock() + // log.Println(c.proto) + return DoCompiledFile(vm, c.proto) +} diff --git a/conf.go b/conf.go index f72b7c9..bda1a1c 100644 --- a/conf.go +++ b/conf.go @@ -33,6 +33,10 @@ type vhost struct { Docroot string `yaml:"docroot"` Hostname string `yaml:"hostname"` ProxyPass string `yaml:"proxypass"` + URLRules []struct { + Prefix string `yaml:"prefix"` + LuaFile string `yaml:"lua_file"` + } `yaml:"url_rules"` } func loadConfig(fn string) (*conf, error) { diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..f95b827 --- /dev/null +++ b/config.yaml @@ -0,0 +1,29 @@ +# vim: set ft=yaml: + +# when provide certficate file, server will listen https and enable http2 +listen: + - + addr: 0.0.0.0 + port: 9001 + certificates: + +# virtual host support +vhost: + - + hostname: www.ratafee.nl + docroot: . + # proxypass: http://nginx:80/ + url_rules: + - prefix: /api/ + lua_file: api.lua +proxy: + http1-proxy: false + http2-proxy: true + # trust the follow domains as local virtual host + # when http2 proxy enabled + localdomains: + - localhost + - localdomain + - 127.0.0.1 + - ratafee.nl + - 98.142.138.194 diff --git a/config_example.yaml b/config_example.yaml index 6ac143f..6a20ba4 100644 --- a/config_example.yaml +++ b/config_example.yaml @@ -20,6 +20,9 @@ vhost: hostname: www.ratafee.nl docroot: /srv/www proxypass: http://nginx:80/ + url_rules: + - prefix: /api/ + lua_file: api.lua proxy: http1-proxy: false http2-proxy: true diff --git a/go.mod b/go.mod index e990b76..4626013 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,18 @@ module github.com/fangdingjun/gserver go 1.13 require ( + github.com/cjoudrey/gluahttp v0.0.0-20190104103309-101c19a37344 github.com/fangdingjun/go-log v0.0.0-20190821073628-ae332053d6dc + github.com/fangdingjun/gopher-redis v0.0.0-20190823083833-93d88f74dd35 github.com/fangdingjun/protolistener v0.0.0-20190821093313-6d5d2138f296 + github.com/go-sql-driver/mysql v1.4.1 // indirect github.com/go-yaml/yaml v2.1.0+incompatible github.com/gorilla/handlers v1.4.2 github.com/gorilla/mux v1.7.3 - github.com/kr/pretty v0.1.0 // indirect + github.com/junhsieh/goexamples v0.0.0-20190721045834-1c67ae74caa6 // indirect + github.com/tengattack/gluasql v0.0.0-20181229041402-2e5ed630c4cf + github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect - gopkg.in/yaml.v2 v2.2.2 // indirect + layeh.com/gopher-json v0.0.0-20190114024228-97fed8db8427 + layeh.com/gopher-luar v1.0.7 ) diff --git a/go.sum b/go.sum index f1e3068..70753b2 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,49 @@ +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cjoudrey/gluahttp v0.0.0-20190104103309-101c19a37344 h1:nTK6O2RY1nHC4jNEJd1nixVP7ygY3QE4CNxCX07dHKU= +github.com/cjoudrey/gluahttp v0.0.0-20190104103309-101c19a37344/go.mod h1:X97UjDTXp+7bayQSFZk2hPvCTmTZIicUjZQRtkwgAKY= github.com/fangdingjun/go-log v0.0.0-20190821073628-ae332053d6dc h1:Tdk7VsmsFo3d0NqHTy3SRoRnkduOxwXgR65gQsq8kXY= github.com/fangdingjun/go-log v0.0.0-20190821073628-ae332053d6dc/go.mod h1:oi7jbIScCbha6TbVzmrJP6igIHt+jcvvEgSJ7Ww1GkI= +github.com/fangdingjun/gopher-redis v0.0.0-20190823083833-93d88f74dd35 h1:ylhL3mOAVv+xPvF7ml4mPJkacGyc4hDAxBRl8/DGATk= +github.com/fangdingjun/gopher-redis v0.0.0-20190823083833-93d88f74dd35/go.mod h1:mJh0kXIkwwPbU3KnTswNRDtHUtA+xlFJRHnbxuFg33Y= github.com/fangdingjun/protolistener v0.0.0-20190821093313-6d5d2138f296 h1:2c6agkdoPVSyvdJ0B+5DhOb1BQpso7a7zlBxXUnttmY= github.com/fangdingjun/protolistener v0.0.0-20190821093313-6d5d2138f296/go.mod h1:RoT81rjdN8gQ1w/z7NiFkxV6VzkT4NZ43XIt0lu8tcc= -github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/junhsieh/goexamples v0.0.0-20190721045834-1c67ae74caa6 h1:oi85CfIzfVPMlTf6pteDMw0S3AjZ9b6Y0OaxfsLCU3M= +github.com/junhsieh/goexamples v0.0.0-20190721045834-1c67ae74caa6/go.mod h1:JNqB8Da6SnlJvmZusESDfgqUkJXO6+/a1by1etsVJ2M= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.9.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.6.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY= +github.com/tengattack/gluasql v0.0.0-20181229041402-2e5ed630c4cf h1:7zGSyMKHHwD0A8iJk0IlP8FhSIOZco4VyzemWYHP1Ck= +github.com/tengattack/gluasql v0.0.0-20181229041402-2e5ed630c4cf/go.mod h1:m4UsFo0sc87XS8WozZBD8HJPC2NKI/nB7w4N58HwemQ= +github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= +github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a h1:Igim7XhdOpBnWPuYJ70XcNpq8q3BCACtVgNfoJxOV7g= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +layeh.com/gopher-json v0.0.0-20190114024228-97fed8db8427 h1:RZkKxMR3jbQxdCEcglq3j7wY3PRJIopAwBlx1RE71X0= +layeh.com/gopher-json v0.0.0-20190114024228-97fed8db8427/go.mod h1:ivKkcY8Zxw5ba0jldhZCYYQfGdb2K6u9tbYK1AwMIBc= +layeh.com/gopher-luar v1.0.7 h1:53iv6CCkRs5wyofZ+qVXcyAYQOIG52s6pt4xkqZdq7k= +layeh.com/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk= diff --git a/log.go b/log.go new file mode 100644 index 0000000..92b8e7a --- /dev/null +++ b/log.go @@ -0,0 +1,17 @@ +package main + +import ( + "github.com/fangdingjun/go-log" +) + +func infoLog(msg string) { + log.Println(msg) +} + +func errorLog(msg string) { + log.Errorln(msg) +} + +func debugLog(msg string) { + log.Debugln(msg) +} diff --git a/lua_handler.go b/lua_handler.go new file mode 100644 index 0000000..1630a94 --- /dev/null +++ b/lua_handler.go @@ -0,0 +1,49 @@ +package main + +import ( + "io/ioutil" + "net" + "net/http" + + "github.com/fangdingjun/go-log" + luar "layeh.com/gopher-luar" +) + +type luaHandler struct { + scriptFile string +} + +func (l *luaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + vm := luaPool.Get() + defer luaPool.Put(vm) + + vm.SetGlobal("request", luar.New(vm, &req{r})) + vm.SetGlobal("response", luar.New(vm, w)) + + if err := runFile(vm, l.scriptFile); err != nil { + log.Errorln(err) + http.Error(w, "server error", http.StatusInternalServerError) + } +} + +type req struct { + *http.Request +} + +func (r1 *req) GetBody() (string, error) { + d, err := ioutil.ReadAll(r1.Body) + return string(d), err +} + +func (r1 *req) GetIP() string { + ip := r1.Header.Get("x-real-ip") + if ip != "" { + return ip + } + ip = r1.Header.Get("x-forwarded-for") + if ip != "" { + return ip + } + host, _, _ := net.SplitHostPort(r1.RemoteAddr) + return host +} diff --git a/pool.go b/pool.go new file mode 100644 index 0000000..318cd05 --- /dev/null +++ b/pool.go @@ -0,0 +1,68 @@ +package main + +import ( + "net/http" + "sync" + + gluahttp "github.com/cjoudrey/gluahttp" + lua "github.com/yuin/gopher-lua" + luajson "layeh.com/gopher-json" + luar "layeh.com/gopher-luar" + mysql "github.com/tengattack/gluasql/mysql" + redis "github.com/fangdingjun/gopher-redis" +) + +// from https://github.com/yuin/gophoer-lua + +type lStatePool struct { + m sync.Mutex + saved []*lua.LState +} + +func (pl *lStatePool) Get() *lua.LState { + pl.m.Lock() + defer pl.m.Unlock() + n := len(pl.saved) + if n == 0 { + return pl.New() + } + x := pl.saved[n-1] + pl.saved = pl.saved[0 : n-1] + return x +} + +func (pl *lStatePool) New() *lua.LState { + L := lua.NewState() + + luajson.Preload(L) + L.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader) + L.PreloadModule("mysql", mysql.Loader) + L.PreloadModule("redis", redis.Loader) + + L.SetGlobal("infolog", luar.New(L, infoLog)) + L.SetGlobal("errorlog", luar.New(L, errorLog)) + L.SetGlobal("debuglog", luar.New(L, debugLog)) + + // setting the L up here. + // load scripts, set global variables, share channels, etc... + return L +} + +func (pl *lStatePool) Put(L *lua.LState) { + pl.m.Lock() + defer pl.m.Unlock() + pl.saved = append(pl.saved, L) +} + +func (pl *lStatePool) Shutdown() { + pl.m.Lock() + defer pl.m.Unlock() + for _, L := range pl.saved { + L.Close() + } +} + +// Global LState pool +var luaPool = &lStatePool{ + saved: make([]*lua.LState, 0, 4), +} diff --git a/server.go b/server.go index 97a874d..119d0d5 100644 --- a/server.go +++ b/server.go @@ -24,12 +24,19 @@ func initServer(c *conf) error { for _, vh := range c.Vhosts { subroute := mux.Host(vh.Hostname) + for _, rule := range vh.URLRules { + subroute.PathPrefix(rule.Prefix).Handler(&luaHandler{rule.LuaFile}) + } subroute.PathPrefix("/").Handler(http.FileServer(http.Dir(vh.Docroot))) } mux.PathPrefix("/debug/").Handler(http.DefaultServeMux) if len(c.Vhosts) > 0 { + rules := c.Vhosts[0].URLRules + for _, rule := range rules { + mux.PathPrefix(rule.Prefix).Handler(&luaHandler{rule.LuaFile}) + } mux.PathPrefix("/").Handler(http.FileServer(http.Dir(c.Vhosts[0].Docroot))) } else { mux.PathPrefix("/").Handler(http.FileServer(http.Dir("/var/www/html")))