├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cluster ├── agent.go ├── clusterd.go ├── config.go ├── const.go ├── request.go ├── response.go └── sender.go ├── config.moon ├── docker-compose.yaml ├── example.go ├── gate ├── gate.go └── proto.go ├── go.mod ├── go.sum ├── lua ├── const.go ├── parse.go ├── seri.go ├── seri_test.go └── value.go ├── moon.lua └── service ├── example.go ├── http.go ├── ping.go └── service.go /.gitignore: -------------------------------------------------------------------------------- 1 | moon 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22 2 | 3 | COPY . /moon 4 | 5 | WORKDIR /moon 6 | 7 | RUN go build -o /usr/bin/moon 8 | 9 | expose 3345 10 | 11 | CMD ["/usr/bin/moon"] 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Zwlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go parameters:q 2 | GOCMD=go 3 | GOBUILD=$(GOCMD) build 4 | GOCLEAN=$(GOCMD) clean 5 | GOTEST=$(GOCMD) test 6 | BINARY_NAME= moon 7 | 8 | all: build 9 | 10 | build: 11 | $(GOBUILD) -o $(BINARY_NAME) -v 12 | test: 13 | $(GOTEST) -v ./... 14 | clean: 15 | $(GOCLEAN) 16 | rm -f $(BINARY_NAME) 17 | run: build 18 | ./$(BINARY_NAME) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Moon —— a sidecar for Skynet 2 | 3 | ## Feature 4 | 5 | 1. 与 Skynet Cluster 互相调用,支持 Skynet Cluster 的 RPC 格式,无缝接入 Skynet 的 Cluster 模式 6 | 2. 不侵入 Skynet ,避免了使用 C 对 Skynet 进行扩展时的编程风险 7 | 3. 由 Go 语言编写,能方便的集成 Go 语言所带来的各种生态优势 8 | 4. API 设计参考 Skynet Cluster,没有集成难度 9 | 5. 简单直观的 Lua 对象封装,可以方便的对 Skynet 集群调用中使用的 Lua 对象进行序列化和反序列化 10 | 6. 性能持平 Skynet Cluster 集群方案 11 | 12 | ## Examples 13 | 14 | service 文件夹中出给出了两个简单的示例: `http.go` 和 `example.go`, 展示了如何处理 Skynet 传递来的 Lua 对象。 15 | 下面的例子展示了 Skynet 节点调用 Moon 节点上 HTTP 服务(call) 的过程,HTTP 是 Moon 自带的一个用于处理 http 请求的服务, 16 | 可以用于替换 Skynet 自带的 http 服务。 17 | 18 | ### example.go 19 | 20 | ```go 21 | func main() { 22 | // initialize services 23 | httpService := service.NewHttpService() 24 | pingService := service.NewPingService() 25 | 26 | // initialize cluster 27 | clusterd := cluster.GetClusterd() 28 | clusterd.Reload(cluster.DefaultConfig{ 29 | "moon": "0.0.0.0:3345", 30 | }) 31 | 32 | // register services 33 | clusterd.Register("http", httpService) 34 | clusterd.Register("ping", pingService) 35 | 36 | // start cluster 37 | clusterd.Open("moon") 38 | 39 | log.Printf("moon start") 40 | 41 | term := make(chan os.Signal, 1) 42 | 43 | signal.Notify(term, os.Interrupt) 44 | 45 | <-term 46 | } 47 | ``` 48 | 49 | ### moon.lua 50 | 51 | ```lua 52 | local function moonHttp(url, opts) 53 | opts = opts or {} 54 | opts.method = opts.method or "GET" 55 | opts.headers = opts.headers or {} 56 | opts.body = opts.body or "" 57 | 58 | opts.noBody = opts.noBody or false 59 | if opts.noHeader == nil then 60 | opts.noHeader = true 61 | end 62 | local ok, msg, code, resp = pcall(cluster.call, "moon", "http", "request", url, opts) 63 | if ok then 64 | return msg, code, resp 65 | else 66 | return false, msg 67 | end 68 | end 69 | 70 | skynet.start(function() 71 | cluster.reload({ 72 | moon = "127.0.0.1:3345", 73 | }) 74 | 75 | local ok, code, resp = moonHttp("https://www..com", { method = "GET" }) 76 | 77 | if ok and resp then 78 | skynet.error(resp.body) 79 | else 80 | skynet.error(code) 81 | end 82 | end) 83 | 84 | ``` 85 | 86 | ### config.moon 87 | 88 | ```lua 89 | thread = 8 90 | logger = nil 91 | harbor = 0 92 | start = "moon" 93 | bootstrap = "snlua bootstrap" -- The service for bootstrap 94 | luaservice = "./service/?.lua;./test/?.lua;./examples/?.lua" 95 | lualoader = "lualib/loader.lua" 96 | cpath = "./cservice/?.so" 97 | snax = "./test/?.lua" 98 | ``` 99 | -------------------------------------------------------------------------------- /cluster/agent.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "fmt" 5 | "log/slog" 6 | "net" 7 | 8 | "github.com/Zwlin98/moon/gate" 9 | "github.com/Zwlin98/moon/lua" 10 | ) 11 | 12 | // execute request from other skynet node 13 | type ClusterAgent interface { 14 | Start() 15 | Exit() 16 | } 17 | 18 | type skynetClusterAgent struct { 19 | conn net.Conn 20 | clusterd Clusterd 21 | gate gate.Gate 22 | 23 | pendingReqs map[uint32]Request 24 | 25 | respChan chan PackedResponse 26 | exit chan struct{} 27 | } 28 | 29 | func NewClusterAgent(gate gate.Gate, conn net.Conn, clusterd Clusterd) ClusterAgent { 30 | return &skynetClusterAgent{ 31 | conn: conn, 32 | clusterd: clusterd, 33 | gate: gate, 34 | 35 | pendingReqs: make(map[uint32]Request), 36 | 37 | respChan: make(chan PackedResponse), 38 | exit: make(chan struct{}), 39 | } 40 | } 41 | 42 | func (ca *skynetClusterAgent) safeSend(resp PackedResponse) bool { 43 | select { 44 | case <-ca.exit: 45 | slog.Info("ClusterAgent exited", "addr", ca.conn.RemoteAddr()) 46 | return false 47 | case ca.respChan <- resp: 48 | return true 49 | } 50 | } 51 | 52 | func (ca *skynetClusterAgent) Start() { 53 | slog.Info("ClusterAgent connected", "addr", ca.conn.RemoteAddr()) 54 | 55 | proto := gate.NewGateProto(ca.conn, ca.conn) 56 | 57 | // Read msg from client 58 | go func() { 59 | for { 60 | msg, err := proto.Read() 61 | if err != nil { 62 | slog.Error("ClusterAgent read error", "addr", ca.conn.RemoteAddr(), "error", err) 63 | ca.Exit() 64 | return 65 | } 66 | ca.dispatch(msg) 67 | } 68 | }() 69 | 70 | // Write response to client 71 | go func() { 72 | for { 73 | select { 74 | case <-ca.exit: 75 | slog.Info("ClusterAgent response channel closed", "addr", ca.conn.RemoteAddr()) 76 | return 77 | case packedResp := <-ca.respChan: 78 | proto.Write(packedResp.Data) 79 | proto.WriteBatch(packedResp.Multi) 80 | } 81 | } 82 | }() 83 | 84 | } 85 | 86 | func (ca *skynetClusterAgent) Exit() { 87 | slog.Info("ClusterAgent exit", "addr", ca.conn.RemoteAddr()) 88 | close(ca.exit) 89 | ca.gate.RemoveClient() 90 | (ca.conn).Close() 91 | } 92 | 93 | func (ca *skynetClusterAgent) dispatch(msg []byte) { 94 | req, err := UnpackRequest(msg) 95 | if err != nil { 96 | slog.Error("ClusterAgent dispatch error", "error", err) 97 | } 98 | session := req.Session 99 | 100 | if pr, ok := ca.pendingReqs[session]; ok { 101 | pr.Msg = append(pr.Msg, req.Msg...) 102 | if req.Completed { 103 | go ca.execute(pr) 104 | delete(ca.pendingReqs, session) 105 | } else { 106 | ca.pendingReqs[session] = pr 107 | } 108 | } else { 109 | if req.Completed { 110 | go ca.execute(req) 111 | } else { 112 | ca.pendingReqs[session] = req 113 | } 114 | } 115 | } 116 | 117 | func (ca *skynetClusterAgent) execute(req Request) { 118 | defer func() { 119 | if r := recover(); r != nil { 120 | slog.Error("ClusterAgent execute panic", "addr", ca.conn.RemoteAddr(), "error", r) 121 | ca.sendError(req, fmt.Errorf("panic: %v", r)) 122 | } 123 | }() 124 | 125 | svc := ca.clusterd.Query(req.Address) 126 | if svc == nil { 127 | ca.sendError(req, fmt.Errorf("service not found: %v", req.Address)) 128 | return 129 | } 130 | 131 | args, err := lua.Deserialize(req.Msg) 132 | if err != nil { 133 | ca.sendError(req, err) 134 | return 135 | } 136 | 137 | ret, err := svc.Execute(args) 138 | if err != nil { 139 | ca.sendError(req, err) 140 | return 141 | } 142 | 143 | if !req.IsPush { 144 | serialized, err := lua.Serialize(ret) 145 | 146 | if err != nil { 147 | ca.sendError(req, err) 148 | return 149 | } 150 | 151 | resp := Response{ 152 | Ok: true, 153 | Session: req.Session, 154 | Msg: serialized, 155 | } 156 | 157 | packedResp, err := PackResponse(resp) 158 | 159 | if err != nil { 160 | ca.sendError(req, err) 161 | return 162 | } 163 | 164 | ok := ca.safeSend(packedResp) 165 | if !ok { 166 | slog.Error("ClusterAgent send response failed", "addr", ca.conn.RemoteAddr()) 167 | } 168 | } 169 | } 170 | 171 | func (ca *skynetClusterAgent) sendError(req Request, err error) { 172 | slog.Warn("ClusterAgent send error", "addr", ca.conn.RemoteAddr(), "error", err) 173 | if req.IsPush { 174 | return 175 | } 176 | ret := []lua.Value{lua.String(err.Error())} 177 | serialized, _ := lua.Serialize(ret) 178 | resp := Response{ 179 | Ok: false, 180 | Session: req.Session, 181 | Msg: serialized, 182 | } 183 | packedResp, _ := PackResponse(resp) 184 | 185 | ca.safeSend(packedResp) 186 | } 187 | -------------------------------------------------------------------------------- /cluster/clusterd.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "fmt" 5 | "log/slog" 6 | "net" 7 | "sync" 8 | 9 | "github.com/Zwlin98/moon/gate" 10 | "github.com/Zwlin98/moon/lua" 11 | "github.com/Zwlin98/moon/service" 12 | ) 13 | 14 | type Clusterd interface { 15 | Reload(ClusterConfig) 16 | 17 | Register(any, service.Service) error 18 | Query(any) service.Service 19 | 20 | Open(string) error 21 | OnConnect(gate gate.Gate, conn net.Conn) 22 | 23 | fetchSender(string) Sender 24 | OnSenderExit(string) 25 | } 26 | 27 | type skynetClusterd struct { 28 | sync.Mutex 29 | 30 | config ClusterConfig 31 | 32 | namedServices map[string]service.Service 33 | 34 | nodeSender sync.Map 35 | 36 | gate map[string]gate.Gate 37 | } 38 | 39 | var globalClusterd Clusterd 40 | var once sync.Once 41 | 42 | func GetClusterd() Clusterd { 43 | once.Do(func() { 44 | globalClusterd = newClusterd() 45 | }) 46 | return globalClusterd 47 | } 48 | 49 | func newClusterd() *skynetClusterd { 50 | return &skynetClusterd{ 51 | namedServices: make(map[string]service.Service), 52 | gate: make(map[string]gate.Gate), 53 | config: make(DefaultConfig), 54 | } 55 | } 56 | 57 | func Call(node string, service string, method string, args []lua.Value) ([]lua.Value, error) { 58 | c := GetClusterd() 59 | client := c.fetchSender(node) 60 | if client == nil { 61 | return nil, fmt.Errorf("no client for node: %s", node) 62 | } 63 | return client.Call(service, method, args) 64 | } 65 | 66 | func Send(node string, service string, method string, args []lua.Value) error { 67 | c := GetClusterd() 68 | client := c.fetchSender(node) 69 | if client == nil { 70 | return fmt.Errorf("no client for node: %s", node) 71 | } 72 | return client.Send(service, method, args) 73 | } 74 | 75 | func (c *skynetClusterd) Query(address any) service.Service { 76 | if addr, ok := address.(string); ok { 77 | if svc, ok := c.namedServices[addr]; ok { 78 | return svc 79 | } 80 | } 81 | return nil 82 | } 83 | 84 | // TODO: int address type 85 | func (c *skynetClusterd) Register(address any, svc service.Service) error { 86 | if addr, ok := address.(string); ok { 87 | if _, ok := c.namedServices[addr]; ok { 88 | return fmt.Errorf("service already registered: %s", addr) 89 | } 90 | c.namedServices[addr] = svc 91 | return nil 92 | } 93 | return fmt.Errorf("invalid address type: %T", address) 94 | } 95 | 96 | func (c *skynetClusterd) Reload(config ClusterConfig) { 97 | if c.config == nil { 98 | c.config = config 99 | return 100 | } 101 | //self address change check 102 | for name, gate := range c.gate { 103 | if config.NodeInfo(name) != gate.Address() { 104 | gate.Stop() 105 | delete(c.gate, name) 106 | c.Open(name) 107 | } 108 | } 109 | // node address change check 110 | c.nodeSender.Range(func(key, value interface{}) bool { 111 | name := key.(string) 112 | client := value.(Sender) 113 | if config.NodeInfo(name) != client.RemoteAddr() { 114 | client.Exit() 115 | c.nodeSender.Delete(name) 116 | } 117 | return true 118 | }) 119 | c.config = config 120 | } 121 | 122 | var mutex sync.Mutex 123 | 124 | func (c *skynetClusterd) fetchSender(name string) Sender { 125 | addr := c.config.NodeInfo(name) 126 | if addr == "" { 127 | return nil 128 | } 129 | // fetch first 130 | if client, ok := c.nodeSender.Load(name); ok { 131 | return client.(Sender) 132 | } 133 | c.Lock() 134 | defer c.Unlock() 135 | // fetch again 136 | if client, ok := c.nodeSender.Load(name); ok { 137 | return client.(Sender) 138 | } 139 | client, error := NewClusterClient(c, name, addr) 140 | if error != nil { 141 | return nil 142 | } 143 | client.Start() 144 | c.nodeSender.Store(name, client) 145 | return client 146 | } 147 | 148 | // OnSenderExit implements Clusterd. 149 | func (c *skynetClusterd) OnSenderExit(name string) { 150 | slog.Info("client removed", "name", name) 151 | c.nodeSender.Delete(name) 152 | } 153 | 154 | func (c *skynetClusterd) OnConnect(gate gate.Gate, conn net.Conn) { 155 | agent := NewClusterAgent(gate, conn, c) 156 | agent.Start() 157 | } 158 | 159 | func (c *skynetClusterd) Open(name string) error { 160 | if c.config == nil { 161 | return fmt.Errorf("cluster config is nil") 162 | } 163 | addr := c.config.NodeInfo(name) 164 | if addr == "" { 165 | return fmt.Errorf("no address for node: %s", name) 166 | } 167 | c.gate[name] = gate.NewGate( 168 | gate.WithAddress(addr), 169 | gate.WithAgent(c), 170 | ) 171 | return c.gate[name].Start() 172 | } 173 | -------------------------------------------------------------------------------- /cluster/config.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | type ClusterConfig interface { 4 | GetNodes() map[string]string 5 | NodeInfo(string) string 6 | } 7 | 8 | type DefaultConfig map[string]string 9 | 10 | func (c DefaultConfig) GetNodes() map[string]string { 11 | return c 12 | } 13 | 14 | func (c DefaultConfig) NodeInfo(node string) string { 15 | return c[node] 16 | } 17 | -------------------------------------------------------------------------------- /cluster/const.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | const ( 4 | MULTI_PART = 0x8000 5 | 6 | REQUEST_SINGLE_NUMBER uint8 = 0x00 7 | REQUEST_SINGLE_STRING uint8 = 0x80 8 | 9 | REQUEST_MULTI_NUMBER uint8 = 0x01 10 | REQUEST_MULTI_NUMBER_PUSH uint8 = 0x41 11 | 12 | REQUEST_MULTI_STRING uint8 = 0x81 13 | REQUEST_MULTI_STRING_PUSH uint8 = 0xc1 14 | 15 | REQUEST_MULTI_PART uint8 = 0x02 16 | REQUEST_MULTI_PART_END uint8 = 0x03 17 | 18 | REQUEST_TRACE uint8 = 0x04 19 | 20 | DEFAULT_BUFFER_SIZE = 0x8200 21 | ) 22 | 23 | const ( 24 | RESPONSE_ERROR uint8 = 0 25 | RESPONSE_OK uint8 = 1 26 | RESPONSE_MULTI_BEGIN uint8 = 2 27 | RESPONSE_MULTI_PART uint8 = 3 28 | RESPONSE_MULTI_END uint8 = 4 29 | RESPONSE_END uint8 = 5 30 | ) 31 | -------------------------------------------------------------------------------- /cluster/request.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | type Request struct { 10 | Address any // uint32 or string 11 | Session uint32 12 | IsPush bool 13 | Msg []byte 14 | 15 | Completed bool // for received request 16 | } 17 | 18 | type PackedRequest struct { 19 | Data []byte 20 | Multi [][]byte 21 | } 22 | 23 | func PackRequest(r Request) (PackedRequest, error) { 24 | var pr = PackedRequest{} 25 | if r.Address == nil { 26 | return pr, fmt.Errorf("address is nil") 27 | } 28 | if r.Session == 0 { 29 | return pr, fmt.Errorf("session is zero") 30 | } 31 | msgSize := uint32(len(r.Msg)) 32 | if msgSize == 0 { 33 | return pr, fmt.Errorf("msg is empty") 34 | } 35 | if msgSize < MULTI_PART { 36 | return packSingleRequest(r) 37 | } 38 | return packMultiRequest(r) 39 | } 40 | 41 | func UnpackRequest(data []byte) (Request, error) { 42 | var r = Request{} 43 | switch data[0] { 44 | case REQUEST_SINGLE_NUMBER: 45 | fallthrough 46 | case REQUEST_SINGLE_STRING: 47 | return unpackSingleRequest(data) 48 | case REQUEST_MULTI_NUMBER: 49 | fallthrough 50 | case REQUEST_MULTI_NUMBER_PUSH: 51 | fallthrough 52 | case REQUEST_MULTI_STRING: 53 | fallthrough 54 | case REQUEST_MULTI_STRING_PUSH: 55 | fallthrough 56 | case REQUEST_MULTI_PART: 57 | fallthrough 58 | case REQUEST_MULTI_PART_END: 59 | return unpackMultiRequest(data) 60 | default: 61 | return r, fmt.Errorf("request type is not supported") 62 | } 63 | } 64 | 65 | func unpackSingleRequest(data []byte) (Request, error) { 66 | var r = Request{} 67 | switch data[0] { 68 | case REQUEST_SINGLE_NUMBER: 69 | r.Address = binary.LittleEndian.Uint32(data[1:]) 70 | r.Session = binary.LittleEndian.Uint32(data[5:]) 71 | r.Msg = data[9:] 72 | case REQUEST_SINGLE_STRING: 73 | nameLen := uint8(data[1]) 74 | r.Address = string(data[2 : 2+nameLen]) 75 | r.Session = binary.LittleEndian.Uint32(data[2+nameLen:]) 76 | r.Msg = data[6+nameLen:] 77 | } 78 | if r.Session == 0 { 79 | r.IsPush = true 80 | } else { 81 | r.IsPush = false 82 | } 83 | r.Completed = true 84 | return r, nil 85 | } 86 | 87 | func unpackMultiRequest(data []byte) (Request, error) { 88 | var r = Request{} 89 | switch data[0] { 90 | case REQUEST_MULTI_NUMBER, REQUEST_MULTI_NUMBER_PUSH: 91 | if data[0] == REQUEST_MULTI_NUMBER_PUSH { 92 | r.IsPush = true 93 | } else { 94 | r.IsPush = false 95 | } 96 | r.Address = binary.LittleEndian.Uint32(data[1:]) 97 | r.Session = binary.LittleEndian.Uint32(data[5:]) 98 | case REQUEST_MULTI_STRING, REQUEST_MULTI_STRING_PUSH: 99 | if data[0] == REQUEST_MULTI_STRING_PUSH { 100 | r.IsPush = true 101 | } else { 102 | r.IsPush = false 103 | } 104 | nameLen := uint8(data[1]) 105 | r.Address = string(data[2 : 2+nameLen]) 106 | r.Session = binary.LittleEndian.Uint32(data[2+nameLen:]) 107 | case REQUEST_MULTI_PART, REQUEST_MULTI_PART_END: 108 | if data[0] == REQUEST_MULTI_PART_END { 109 | r.Completed = true 110 | } 111 | r.Session = binary.LittleEndian.Uint32(data[1:]) 112 | r.Msg = data[5:] 113 | } 114 | return r, nil 115 | } 116 | 117 | func packSingleRequest(r Request) (PackedRequest, error) { 118 | var pr = PackedRequest{} 119 | var buf = bytes.NewBuffer(nil) 120 | switch r.Address.(type) { 121 | case uint32: 122 | address := r.Address.(uint32) 123 | // 1 byte for request type 124 | buf.WriteByte(REQUEST_SINGLE_NUMBER) 125 | // 4 bytes for address 126 | binary.Write(buf, binary.LittleEndian, address) 127 | // 4 bytes for session 128 | if !r.IsPush { 129 | binary.Write(buf, binary.LittleEndian, r.Session) 130 | } else { 131 | binary.Write(buf, binary.LittleEndian, uint32(0)) 132 | } 133 | // copy msg 134 | buf.Write(r.Msg) 135 | case string: 136 | name := r.Address.(string) 137 | nameLen := uint8(len(name)) 138 | if nameLen < 1 || nameLen > 255 { 139 | return PackedRequest{}, fmt.Errorf("name length error") 140 | } 141 | // 1 byte for request type 142 | buf.WriteByte(REQUEST_SINGLE_STRING) 143 | // 1 byte for name length 144 | buf.WriteByte(uint8(nameLen)) 145 | // `nameLen` bytes for name 146 | buf.Write([]byte(name)) 147 | // 4 bytes for session 148 | if !r.IsPush { 149 | binary.Write(buf, binary.LittleEndian, r.Session) 150 | } else { 151 | binary.Write(buf, binary.LittleEndian, uint32(0)) 152 | } 153 | // copy msg 154 | buf.Write(r.Msg) 155 | default: 156 | return PackedRequest{}, fmt.Errorf("address type is not supported") 157 | } 158 | pr.Data = buf.Bytes() 159 | return pr, nil 160 | } 161 | 162 | func packMultiRequest(r Request) (PackedRequest, error) { 163 | var pr = PackedRequest{ 164 | Multi: make([][]byte, 0), 165 | } 166 | msgSize := uint32(len(r.Msg)) 167 | 168 | bufData := bytes.NewBuffer(nil) 169 | switch r.Address.(type) { 170 | case uint32: 171 | address := r.Address.(uint32) 172 | // 1 byte for request type 173 | if r.IsPush { 174 | bufData.WriteByte(REQUEST_MULTI_NUMBER_PUSH) 175 | } else { 176 | bufData.WriteByte(REQUEST_MULTI_NUMBER) 177 | } 178 | // 4 bytes for address 179 | binary.Write(bufData, binary.LittleEndian, address) 180 | // 4 bytes for session 181 | binary.Write(bufData, binary.LittleEndian, r.Session) 182 | // 4 bytes for msg size 183 | binary.Write(bufData, binary.LittleEndian, msgSize) 184 | case string: 185 | name := r.Address.(string) 186 | nameLen := uint16(len(name)) 187 | if nameLen < 1 || nameLen > 255 { 188 | return PackedRequest{}, fmt.Errorf("name length error") 189 | } 190 | // 1 byte for request type 191 | if r.IsPush { 192 | bufData.WriteByte(REQUEST_MULTI_STRING_PUSH) 193 | } else { 194 | bufData.WriteByte(REQUEST_MULTI_STRING) 195 | } 196 | // 1 byte for name length 197 | bufData.WriteByte(uint8(nameLen)) 198 | // `nameLen` bytes for name 199 | bufData.Write([]byte(name)) 200 | // 4 bytes for session 201 | binary.Write(bufData, binary.LittleEndian, r.Session) 202 | // 4 bytes for msg size 203 | binary.Write(bufData, binary.LittleEndian, msgSize) 204 | default: 205 | return PackedRequest{}, fmt.Errorf("address type is not supported") 206 | } 207 | pr.Data = bufData.Bytes() 208 | 209 | part := int((msgSize-1)/MULTI_PART + 1) 210 | for i := 0; i < part; i++ { 211 | partBuf := bytes.NewBuffer(nil) 212 | var s uint32 213 | var reqType uint8 214 | if msgSize > MULTI_PART { 215 | s = MULTI_PART 216 | reqType = REQUEST_MULTI_PART 217 | } else { 218 | s = msgSize 219 | reqType = REQUEST_MULTI_PART_END 220 | } 221 | partBuf.WriteByte(reqType) 222 | // 4 bytes for session 223 | binary.Write(partBuf, binary.LittleEndian, r.Session) 224 | 225 | partStart := i * int(MULTI_PART) 226 | partEnd := partStart + int(s) 227 | 228 | // copy msg 229 | partBuf.Write(r.Msg[partStart:partEnd]) 230 | 231 | pr.Multi = append(pr.Multi, partBuf.Bytes()) 232 | msgSize -= s 233 | } 234 | 235 | return pr, nil 236 | } 237 | -------------------------------------------------------------------------------- /cluster/response.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | type Response struct { 10 | Ok bool 11 | Session uint32 12 | Msg []byte 13 | 14 | Padding uint8 // for received response 15 | } 16 | 17 | type PackedResponse struct { 18 | Data []byte 19 | Multi [][]byte 20 | } 21 | 22 | func PackResponse(r Response) (PackedResponse, error) { 23 | if !r.Ok { 24 | return packSingleResponse(r) 25 | } 26 | msgSize := uint32(len(r.Msg)) 27 | if msgSize > MULTI_PART { 28 | return packMultiResponse(r) 29 | } 30 | return packSingleResponse(r) 31 | } 32 | 33 | func UnpackResponse(data []byte) (Response, error) { 34 | if len(data) < 5 { 35 | return Response{}, fmt.Errorf("response data is too short") 36 | } 37 | var r = Response{} 38 | r.Session = binary.LittleEndian.Uint32(data) 39 | switch data[4] { 40 | case RESPONSE_OK: 41 | r.Ok = true 42 | r.Msg = data[5:] 43 | r.Padding = RESPONSE_END 44 | case RESPONSE_ERROR: 45 | r.Ok = false 46 | r.Msg = data[5:] 47 | r.Padding = RESPONSE_MULTI_END 48 | case RESPONSE_MULTI_BEGIN: 49 | r.Ok = true 50 | r.Padding = RESPONSE_MULTI_BEGIN 51 | case RESPONSE_MULTI_PART: 52 | r.Msg = data[5:] 53 | r.Padding = RESPONSE_MULTI_PART 54 | case RESPONSE_MULTI_END: 55 | r.Msg = data[5:] 56 | r.Padding = RESPONSE_MULTI_END 57 | default: 58 | return r, fmt.Errorf("response type is not supported") 59 | } 60 | return r, nil 61 | } 62 | 63 | func packSingleResponse(r Response) (PackedResponse, error) { 64 | var pr = PackedResponse{} 65 | buf := bytes.NewBuffer(nil) 66 | 67 | // 4 bytes for session 68 | binary.Write(buf, binary.LittleEndian, r.Session) 69 | // 1 byte for request type 70 | if r.Ok { 71 | binary.Write(buf, binary.LittleEndian, RESPONSE_OK) 72 | } else { 73 | binary.Write(buf, binary.LittleEndian, RESPONSE_ERROR) 74 | // truncate err message if it's too long 75 | if len(r.Msg) > MULTI_PART { 76 | r.Msg = r.Msg[:MULTI_PART] 77 | } 78 | } 79 | 80 | buf.Write(r.Msg) 81 | 82 | pr.Data = buf.Bytes() 83 | return pr, nil 84 | } 85 | 86 | func packMultiResponse(r Response) (PackedResponse, error) { 87 | var pr = PackedResponse{ 88 | Data: make([]byte, 9), 89 | Multi: make([][]byte, 0), 90 | } 91 | msgSize := uint32(len(r.Msg)) 92 | 93 | // 4 bytes for session 94 | binary.LittleEndian.PutUint32(pr.Data, r.Session) 95 | // 1 byte for request type 96 | pr.Data[4] = RESPONSE_MULTI_BEGIN 97 | // 4 bytes for msg size 98 | binary.LittleEndian.PutUint32(pr.Data[5:], msgSize) 99 | 100 | part := int((msgSize-1)/MULTI_PART + 1) 101 | for i := 0; i < part; i++ { 102 | bufPart := bytes.NewBuffer(nil) 103 | var s uint32 104 | var respType uint8 105 | if msgSize > MULTI_PART { 106 | s = MULTI_PART 107 | respType = RESPONSE_MULTI_PART 108 | } else { 109 | s = msgSize 110 | respType = RESPONSE_MULTI_END 111 | } 112 | // 4 bytes for session 113 | binary.Write(bufPart, binary.LittleEndian, r.Session) 114 | // 1 byte for request type 115 | bufPart.WriteByte(respType) 116 | 117 | partStart := i * int(MULTI_PART) 118 | partEnd := partStart + int(s) 119 | 120 | // copy msg 121 | bufPart.Write(r.Msg[partStart:partEnd]) 122 | 123 | pr.Multi = append(pr.Multi, bufPart.Bytes()) 124 | 125 | msgSize -= s 126 | } 127 | 128 | return pr, nil 129 | } 130 | -------------------------------------------------------------------------------- /cluster/sender.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "fmt" 5 | "log/slog" 6 | "net" 7 | "sync" 8 | "sync/atomic" 9 | 10 | "github.com/Zwlin98/moon/gate" 11 | "github.com/Zwlin98/moon/lua" 12 | ) 13 | 14 | // call/send other skynet node 15 | type Sender interface { 16 | RemoteAddr() string 17 | 18 | Call(string, string, []lua.Value) ([]lua.Value, error) 19 | Send(string, string, []lua.Value) error 20 | 21 | Start() 22 | Exit() 23 | } 24 | 25 | type skynetSender struct { 26 | clusterd Clusterd 27 | remoteName string 28 | remoteAddr string 29 | conn net.Conn 30 | 31 | session uint32 32 | 33 | pendingResponse map[uint32]Response 34 | pendingRespChan sync.Map 35 | 36 | reqChan chan PackedRequest 37 | exit chan struct{} 38 | } 39 | 40 | func NewClusterClient(clusterd Clusterd, name string, addr string) (Sender, error) { 41 | conn, err := net.Dial("tcp", addr) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | client := skynetSender{ 47 | clusterd: clusterd, 48 | remoteName: name, 49 | remoteAddr: addr, 50 | conn: conn, 51 | session: 1, 52 | reqChan: make(chan PackedRequest), 53 | pendingResponse: make(map[uint32]Response), 54 | exit: make(chan struct{}), 55 | } 56 | 57 | return &client, nil 58 | } 59 | 60 | func (sc *skynetSender) Start() { 61 | proto := gate.NewGateProto(sc.conn, sc.conn) 62 | 63 | go func() { 64 | for { 65 | msg, err := proto.Read() 66 | if err != nil { 67 | slog.Error("ClusterClient read error", "addr", sc.conn.RemoteAddr(), "error", err) 68 | sc.Exit() 69 | return 70 | } 71 | sc.dispatch(msg) 72 | } 73 | }() 74 | 75 | go func() { 76 | for { 77 | select { 78 | case <-sc.exit: 79 | slog.Info("ClusterClient exited", "name", sc.name()) 80 | return 81 | case req := <-sc.reqChan: 82 | err := proto.Write(req.Data) 83 | if err != nil { 84 | slog.Error("ClusterClient failed to write message", "name", sc.name(), "error", err) 85 | sc.Exit() 86 | return 87 | } 88 | err = proto.WriteBatch(req.Multi) 89 | if err != nil { 90 | slog.Error("ClusterClient failed to write batch message", "name", sc.name(), "error", err) 91 | sc.Exit() 92 | return 93 | } 94 | } 95 | } 96 | }() 97 | } 98 | 99 | func (sc *skynetSender) callRet(resp Response) { 100 | session := resp.Session 101 | retChan, ok := sc.pendingRespChan.Load(session) 102 | if ok { 103 | retChan.(chan Response) <- resp 104 | } else { 105 | slog.Error("ClusterClient callRet failed, no pending response", "session", session, "name", sc.name()) 106 | } 107 | } 108 | 109 | func (sc *skynetSender) callError(session uint32, msg string) { 110 | sc.callRet(Response{ 111 | Session: session, 112 | Ok: false, 113 | Msg: []byte(msg), 114 | }) 115 | } 116 | 117 | func (sc *skynetSender) packCall(service string, method string, args []lua.Value, isPush bool) (PackedRequest, uint32, error) { 118 | realArgs := []lua.Value{lua.String(method)} 119 | realArgs = append(realArgs, args...) 120 | 121 | packedArgs, err := lua.Serialize(realArgs) 122 | if err != nil { 123 | return PackedRequest{}, 0, err 124 | } 125 | 126 | session := atomic.AddUint32(&sc.session, 1) 127 | 128 | req := Request{ 129 | Address: service, 130 | Session: session, 131 | IsPush: isPush, 132 | Msg: packedArgs, 133 | } 134 | 135 | packedReq, err := PackRequest(req) 136 | 137 | return packedReq, session, err 138 | } 139 | 140 | // Call implements Client. 141 | func (sc *skynetSender) Call(service string, method string, args []lua.Value) ([]lua.Value, error) { 142 | packReq, session, err := sc.packCall(service, method, args, false) 143 | 144 | if err != nil { 145 | return nil, err 146 | } 147 | 148 | respChan := make(chan Response) 149 | sc.pendingRespChan.Store(session, respChan) 150 | defer sc.pendingRespChan.Delete(session) 151 | 152 | select { 153 | case <-sc.exit: 154 | return nil, fmt.Errorf("ClusterClient %s is exited [CallOut]", sc.name()) 155 | case sc.reqChan <- packReq: 156 | } 157 | 158 | select { 159 | case <-sc.exit: 160 | return nil, fmt.Errorf("session %d, ClusterClient %s is exited [Waiting CallRet]", session, sc.name()) 161 | case resp := <-respChan: 162 | if resp.Ok { 163 | return lua.Deserialize(resp.Msg) 164 | } else { 165 | return nil, fmt.Errorf("remote call failed: %s", resp.Msg) 166 | } 167 | } 168 | } 169 | 170 | func (sc *skynetSender) Send(service string, method string, args []lua.Value) error { 171 | packReq, _, err := sc.packCall(service, method, args, true) 172 | if err != nil { 173 | return err 174 | } 175 | select { 176 | case <-sc.exit: 177 | return fmt.Errorf("ClusterClient %s is exited [SendOut]", sc.name()) 178 | case sc.reqChan <- packReq: 179 | return nil 180 | } 181 | } 182 | 183 | func (sc *skynetSender) Exit() { 184 | slog.Info("ClusterClient exit", "name", sc.name()) 185 | close(sc.exit) 186 | sc.conn.Close() 187 | sc.clusterd.OnSenderExit(sc.name()) 188 | } 189 | 190 | func (sc *skynetSender) RemoteAddr() string { 191 | return sc.remoteAddr 192 | } 193 | 194 | func (sc *skynetSender) dispatch(msg []byte) { 195 | resp, err := UnpackResponse(msg) 196 | if err != nil { 197 | slog.Error("failed to unpack response", "error", err) 198 | return 199 | } 200 | switch resp.Padding { 201 | case RESPONSE_END: 202 | sc.callRet(resp) 203 | case RESPONSE_MULTI_BEGIN: 204 | sc.pendingResponse[resp.Session] = resp 205 | case RESPONSE_MULTI_PART: 206 | prevResp, ok := sc.pendingResponse[resp.Session] 207 | if !ok { 208 | slog.Warn("unexpected multi part response") 209 | sc.callError(resp.Session, "unexpected multi part response") 210 | return 211 | } else { 212 | prevResp.Msg = append(prevResp.Msg, resp.Msg...) 213 | sc.pendingResponse[resp.Session] = prevResp 214 | } 215 | case RESPONSE_MULTI_END: 216 | prevResp, ok := sc.pendingResponse[resp.Session] 217 | if !ok { 218 | slog.Warn("unexpected multi end response") 219 | sc.callError(resp.Session, "unexpected multi end response") 220 | return 221 | } else { 222 | prevResp.Msg = append(prevResp.Msg, resp.Msg...) 223 | sc.callRet(prevResp) 224 | delete(sc.pendingResponse, resp.Session) 225 | } 226 | } 227 | } 228 | 229 | func (sc *skynetSender) name() string { 230 | return sc.remoteName 231 | } 232 | -------------------------------------------------------------------------------- /config.moon: -------------------------------------------------------------------------------- 1 | thread = 8 2 | logger = nil 3 | harbor = 0 4 | start = "moon" 5 | bootstrap = "snlua bootstrap" -- The service for bootstrap 6 | luaservice = "./service/?.lua;./test/?.lua;./examples/?.lua" 7 | lualoader = "lualib/loader.lua" 8 | cpath = "./cservice/?.so" 9 | snax = "./test/?.lua" 10 | 11 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | moon: 3 | build: . 4 | ports: 5 | - "3345:3345" 6 | restart: always 7 | -------------------------------------------------------------------------------- /example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log/slog" 5 | "os" 6 | "os/signal" 7 | 8 | "github.com/Zwlin98/moon/cluster" 9 | "github.com/Zwlin98/moon/service" 10 | ) 11 | 12 | func main() { 13 | // initialize services 14 | httpService := service.NewHttpService() 15 | pingService := service.NewPingService() 16 | 17 | // initialize cluster 18 | clusterd := cluster.GetClusterd() 19 | clusterd.Reload(cluster.DefaultConfig{ 20 | "moon": "0.0.0.0:3345", 21 | }) 22 | 23 | // register services 24 | clusterd.Register("http", httpService) 25 | clusterd.Register("ping", pingService) 26 | 27 | // start cluster 28 | clusterd.Open("moon") 29 | 30 | slog.Info("moon started") 31 | 32 | term := make(chan os.Signal, 1) 33 | 34 | signal.Notify(term, os.Interrupt) 35 | 36 | <-term 37 | } 38 | -------------------------------------------------------------------------------- /gate/gate.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "log/slog" 5 | "net" 6 | "sync/atomic" 7 | ) 8 | 9 | type Gate interface { 10 | Start() error 11 | Stop() 12 | Address() string 13 | 14 | AddClient() 15 | RemoveClient() 16 | } 17 | 18 | type GateOption func(*skynetGate) 19 | 20 | type GateAgent interface { 21 | OnConnect(g Gate, conn net.Conn) 22 | } 23 | 24 | type skynetGate struct { 25 | address string 26 | listener net.Listener 27 | maxClient int32 28 | clientCount int32 29 | 30 | agent GateAgent 31 | } 32 | 33 | func assert(condition bool, msg string) { 34 | if !condition { 35 | panic(msg) 36 | } 37 | } 38 | 39 | func NewGate(opt ...GateOption) *skynetGate { 40 | g := &skynetGate{} 41 | for _, o := range opt { 42 | o(g) 43 | } 44 | if g.maxClient == 0 { 45 | g.maxClient = 1024 46 | } 47 | assert(g.address != "", "Failed to create new gate, Address is empty") 48 | assert(g.clientCount == 0, "Failed to create new gate, ClientCount is not zero") 49 | assert(g.agent != nil, "Failed to create new gate, Agent is nil") 50 | return g 51 | } 52 | 53 | func (g *skynetGate) Start() (err error) { 54 | g.listener, err = net.Listen("tcp", g.address) 55 | if err != nil { 56 | return err 57 | } 58 | slog.Info("gate started", "address", g.address) 59 | go g.listenLoop() 60 | return nil 61 | } 62 | 63 | func (g *skynetGate) listenLoop() { 64 | for { 65 | conn, err := g.listener.Accept() 66 | if err != nil { 67 | slog.Error("failed to accept new client", "error", err.Error()) 68 | continue 69 | } 70 | if g.clientCount >= g.maxClient { 71 | slog.Warn("client count exceed max client", "clientCount", g.clientCount, "maxClient", g.maxClient) 72 | } 73 | g.AddClient() 74 | slog.Info("new client connected", "remoteAddr", conn.RemoteAddr().String(), "clientCount", g.clientCount) 75 | g.agent.OnConnect(g, conn) 76 | } 77 | } 78 | 79 | func (g *skynetGate) Address() string { 80 | return g.address 81 | } 82 | 83 | func (g *skynetGate) Stop() { 84 | g.listener.Close() 85 | } 86 | 87 | func (g *skynetGate) AddClient() { 88 | atomic.AddInt32(&g.clientCount, 1) 89 | } 90 | 91 | func (g *skynetGate) RemoveClient() { 92 | atomic.AddInt32(&g.clientCount, -1) 93 | } 94 | 95 | func WithAddress(address string) GateOption { 96 | return func(g *skynetGate) { 97 | g.address = address 98 | } 99 | } 100 | 101 | func WithMaxClient(maxClient int32) GateOption { 102 | return func(g *skynetGate) { 103 | g.maxClient = maxClient 104 | } 105 | } 106 | 107 | func WithAgent(agent GateAgent) GateOption { 108 | return func(g *skynetGate) { 109 | g.agent = agent 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /gate/proto.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | type GateProto interface { 11 | Read() ([]byte, error) 12 | Write([]byte) error 13 | WriteBatch([][]byte) error 14 | } 15 | 16 | type skynetGateProto struct { 17 | reader *bufio.Reader 18 | writer *bufio.Writer 19 | } 20 | 21 | func NewGateProto(rd io.Reader, wd io.Writer) GateProto { 22 | return &skynetGateProto{ 23 | reader: bufio.NewReader(rd), 24 | writer: bufio.NewWriter(wd), 25 | } 26 | } 27 | 28 | func (gp *skynetGateProto) Read() ([]byte, error) { 29 | var sz uint16 30 | err := binary.Read(gp.reader, binary.BigEndian, &sz) 31 | if err != nil { 32 | return nil, err 33 | } 34 | buf := make([]byte, sz) 35 | _, err = io.ReadFull(gp.reader, buf) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return buf, nil 40 | } 41 | 42 | func (gp *skynetGateProto) writeMsg(msg []byte) error { 43 | if len(msg) > 0x10000 { 44 | return fmt.Errorf("message too long") 45 | } 46 | sz := uint16(len(msg)) 47 | err := binary.Write(gp.writer, binary.BigEndian, sz) 48 | if err != nil { 49 | return err 50 | } 51 | _, err = gp.writer.Write(msg) 52 | return err 53 | } 54 | 55 | func (gp *skynetGateProto) Write(msg []byte) error { 56 | if err := gp.writeMsg(msg); err != nil { 57 | return err 58 | } 59 | return gp.writer.Flush() 60 | } 61 | 62 | func (gp *skynetGateProto) WriteBatch(msgs [][]byte) error { 63 | for _, msg := range msgs { 64 | if err := gp.writeMsg(msg); err != nil { 65 | return err 66 | } 67 | } 68 | return gp.writer.Flush() 69 | } 70 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Zwlin98/moon 2 | 3 | go 1.22.2 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zwlin98/moon/b50b5b5383fbdee6de1cc70e2034631bd7c5a7f0/go.sum -------------------------------------------------------------------------------- /lua/const.go: -------------------------------------------------------------------------------- 1 | package lua 2 | 3 | // For serialization and deserialization 4 | const ( 5 | TYPE_NIL uint8 = 0 6 | TYPE_BOOLEAN uint8 = 1 7 | TYPE_NUMBER uint8 = 2 8 | TYPE_USERDATA uint8 = 3 9 | TYPE_SHORT_STRING uint8 = 4 10 | TYPE_LONG_STRING uint8 = 5 11 | TYPE_TABLE uint8 = 6 12 | ) 13 | 14 | // For serialization and deserialization 15 | const ( 16 | TYPE_NUMBER_ZERO uint8 = 0 // 0 17 | TYPE_NUMBER_BYTE uint8 = 1 // 8 bit 18 | TYPE_NUMBER_WORD uint8 = 2 // 16 bit 19 | TYPE_NUMBER_DWORD uint8 = 4 // 32 bit 20 | TYPE_NUMBER_QWORD uint8 = 6 // 64 bit 21 | 22 | TYPE_NUMBER_REAL uint8 = 8 // real 23 | ) 24 | 25 | const ( 26 | LUA_NIL uint8 = 0 27 | LUA_BOOLEAN uint8 = 1 28 | LUA_INTEGER uint8 = 2 29 | LUA_REAL uint8 = 3 30 | LUA_STRING uint8 = 4 31 | LUA_TABLE uint8 = 5 32 | ) 33 | -------------------------------------------------------------------------------- /lua/parse.go: -------------------------------------------------------------------------------- 1 | package lua 2 | 3 | func MustNil(v Value) { 4 | _, ok := v.(Nil) 5 | if !ok { 6 | panic("not a nil") 7 | } 8 | } 9 | 10 | func MustString(v Value) string { 11 | s, ok := v.(String) 12 | if !ok { 13 | panic("not a string") 14 | } 15 | return string(s) 16 | } 17 | 18 | func MustBoolean(v Value) bool { 19 | b, ok := v.(Boolean) 20 | if !ok { 21 | panic("not a boolean") 22 | } 23 | return bool(b) 24 | } 25 | 26 | func MustInteger(v Value) int64 { 27 | i, ok := v.(Integer) 28 | if !ok { 29 | panic("not an integer") 30 | } 31 | return int64(i) 32 | } 33 | 34 | func MustReal(v Value) float64 { 35 | r, ok := v.(Real) 36 | if !ok { 37 | panic("not a real") 38 | } 39 | return float64(r) 40 | } 41 | 42 | func MustTable(v Value) Table { 43 | t, ok := v.(Table) 44 | if !ok { 45 | panic("not a table") 46 | } 47 | return t 48 | } 49 | -------------------------------------------------------------------------------- /lua/seri.go: -------------------------------------------------------------------------------- 1 | package lua 2 | 3 | // Lua 对象在 Go 中的简单表示,用于序列化和反序列化 4 | // 1. 不支持元表的序列化/反序列化 5 | // 2. 不支持函数, 线程, 用户数据, 轻量用户数据的序列化/反序列化 6 | // (LUA_TFUNCTION, LUA_TTHREAD, LUA_TUSERDATA, LUA_TLIGHTUSERDATA) 7 | // 3. 支持纯 Lua Table 数组与 Go Slice 的序列化/反序列化 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "fmt" 12 | "io" 13 | ) 14 | 15 | const MAX_COOKIE = 32 16 | 17 | func combineType(t uint8, v uint8) uint8 { 18 | return t | (v << 3) 19 | } 20 | 21 | func Serialize(values []Value) ([]byte, error) { 22 | buf := bytes.NewBuffer(nil) 23 | for _, v := range values { 24 | if err := serilizeLuaValue(buf, v, 0); err != nil { 25 | return nil, err 26 | } 27 | } 28 | return buf.Bytes(), nil 29 | } 30 | 31 | func Deserialize(data []byte) ([]Value, error) { 32 | buf := bytes.NewBuffer(data) 33 | values := make([]Value, 0) 34 | for { 35 | value, err := deserilizeLuaValue(buf) 36 | if err == io.EOF { 37 | break 38 | } 39 | if err != nil { 40 | return nil, err 41 | } 42 | values = append(values, value) 43 | } 44 | return values, nil 45 | } 46 | 47 | func serilizeLuaValue(buf *bytes.Buffer, v Value, depth int) error { 48 | if depth > 32 { 49 | return fmt.Errorf("serialize can't pack too deep") 50 | } 51 | switch v.LuaType() { 52 | case LUA_NIL: 53 | serilizeNil(buf) 54 | case LUA_BOOLEAN: 55 | serilizeBoolean(buf, v.(Boolean)) 56 | case LUA_INTEGER: 57 | serilizeInteger(buf, v.(Integer)) 58 | case LUA_REAL: 59 | serilizeReal(buf, v.(Real)) 60 | case LUA_STRING: 61 | serilizeString(buf, v.(String)) 62 | case LUA_TABLE: 63 | err := serilizeTable(buf, v.(Table), depth+1) 64 | if err != nil { 65 | return err 66 | } 67 | default: 68 | return fmt.Errorf("unsupported lua value type: %v", v.LuaType()) 69 | } 70 | return nil 71 | } 72 | 73 | func serilizeNil(buf *bytes.Buffer) { 74 | buf.WriteByte(TYPE_NIL) 75 | } 76 | 77 | func serilizeBoolean(buf *bytes.Buffer, b Boolean) { 78 | if b { 79 | buf.WriteByte(combineType(TYPE_BOOLEAN, 1)) 80 | } else { 81 | buf.WriteByte(combineType(TYPE_BOOLEAN, 0)) 82 | } 83 | } 84 | 85 | func serilizeReal(buf *bytes.Buffer, v Real) { 86 | buf.WriteByte(combineType(TYPE_NUMBER, TYPE_NUMBER_REAL)) 87 | binary.Write(buf, binary.LittleEndian, v) 88 | } 89 | 90 | func serilizeTableArray(buf *bytes.Buffer, array []Value, depth int) error { 91 | arrLen := len(array) 92 | if arrLen >= MAX_COOKIE-1 { 93 | buf.WriteByte(combineType(TYPE_TABLE, MAX_COOKIE-1)) 94 | serilizeInteger(buf, Integer(arrLen)) 95 | } else { 96 | buf.WriteByte(combineType(TYPE_TABLE, uint8(arrLen))) 97 | } 98 | for _, v := range array { 99 | if v.LuaType() == LUA_NIL { 100 | return fmt.Errorf("array value can't be nil") 101 | } 102 | err := serilizeLuaValue(buf, v, depth+1) 103 | if err != nil { 104 | return err 105 | } 106 | } 107 | return nil 108 | } 109 | 110 | func serilizeTable(buf *bytes.Buffer, table Table, depth int) error { 111 | if table.Array != nil && len(table.Array) > 0 { 112 | serilizeTableArray(buf, table.Array, depth+1) 113 | } else { 114 | buf.WriteByte(combineType(TYPE_TABLE, 0)) 115 | } 116 | if table.Hash != nil { 117 | for k, v := range table.Hash { 118 | if k.LuaType() == LUA_TABLE || k.LuaType() == LUA_NIL { 119 | return fmt.Errorf("table key can't be table, array or nil") 120 | } 121 | if v.LuaType() == LUA_NIL { 122 | return fmt.Errorf("table value can't be nil") 123 | } 124 | err := serilizeLuaValue(buf, k, depth+1) 125 | if err != nil { 126 | return err 127 | } 128 | err = serilizeLuaValue(buf, v, depth+1) 129 | if err != nil { 130 | return err 131 | } 132 | } 133 | } 134 | serilizeNil(buf) // end of table 135 | return nil 136 | } 137 | 138 | func serilizeString(buf *bytes.Buffer, luaString String) { 139 | sz := len(luaString) 140 | if sz < MAX_COOKIE { 141 | buf.WriteByte(combineType(TYPE_SHORT_STRING, uint8(sz))) 142 | if sz > 0 { 143 | buf.Write([]byte(luaString)) 144 | } 145 | return 146 | } 147 | if sz < 0x10000 { 148 | buf.WriteByte(combineType(TYPE_LONG_STRING, 2)) 149 | binary.Write(buf, binary.LittleEndian, uint16(sz)) 150 | } else { 151 | buf.WriteByte(combineType(TYPE_LONG_STRING, 4)) 152 | binary.Write(buf, binary.LittleEndian, uint32(sz)) 153 | } 154 | buf.Write([]byte(luaString)) 155 | } 156 | 157 | func serilizeInteger(buf *bytes.Buffer, v Integer) { 158 | if v == 0 { 159 | buf.WriteByte(combineType(TYPE_NUMBER, TYPE_NUMBER_ZERO)) 160 | return 161 | } 162 | if (Integer)(int32(v)) != v { 163 | buf.WriteByte(combineType(TYPE_NUMBER, TYPE_NUMBER_QWORD)) 164 | binary.Write(buf, binary.LittleEndian, int64(v)) 165 | return 166 | } 167 | if v < 0 { 168 | buf.WriteByte(combineType(TYPE_NUMBER, TYPE_NUMBER_DWORD)) 169 | binary.Write(buf, binary.LittleEndian, int32(v)) 170 | return 171 | } 172 | if v < 0x100 { 173 | buf.WriteByte(combineType(TYPE_NUMBER, TYPE_NUMBER_BYTE)) 174 | buf.WriteByte(uint8(v)) 175 | return 176 | } 177 | if v < 0x10000 { 178 | buf.WriteByte(combineType(TYPE_NUMBER, TYPE_NUMBER_WORD)) 179 | binary.Write(buf, binary.LittleEndian, uint16(v)) 180 | return 181 | } 182 | buf.WriteByte(combineType(TYPE_NUMBER, TYPE_NUMBER_DWORD)) 183 | binary.Write(buf, binary.LittleEndian, uint32(v)) 184 | } 185 | 186 | func deserilizeLuaValue(buf *bytes.Buffer) (Value, error) { 187 | head, err := buf.ReadByte() 188 | if err != nil { 189 | return nil, err 190 | } 191 | typ, cookie := head&0x07, head>>3 192 | switch typ { 193 | case TYPE_NIL: 194 | return Nil{}, nil 195 | case TYPE_BOOLEAN: 196 | if cookie == 0 { 197 | return Boolean(false), nil 198 | } else { 199 | return Boolean(true), nil 200 | } 201 | case TYPE_NUMBER: 202 | if cookie == TYPE_NUMBER_REAL { 203 | return deserilizeReal(buf) 204 | } else { 205 | return deserilizeInteger(buf, cookie) 206 | } 207 | case TYPE_USERDATA: 208 | var pointer uint64 // skip 209 | err := binary.Read(buf, binary.LittleEndian, &pointer) 210 | return Nil{}, err 211 | case TYPE_SHORT_STRING: 212 | b := make([]byte, cookie) 213 | _, err := buf.Read(b) 214 | return String(string(b)), err 215 | case TYPE_LONG_STRING: 216 | return deserilizeLongString(buf, cookie) 217 | case TYPE_TABLE: 218 | return deserilizeTable(buf, cookie) 219 | default: 220 | return nil, fmt.Errorf("unsupported lua value type: %v", typ) 221 | } 222 | } 223 | 224 | func deserilizeTable(buf *bytes.Buffer, arrLen uint8) (Value, error) { 225 | var arrSize Integer 226 | if arrLen == MAX_COOKIE-1 { 227 | var head uint8 228 | err := binary.Read(buf, binary.LittleEndian, &head) 229 | if err != nil { 230 | return nil, err 231 | } 232 | typ, cookie := head&0x07, head>>3 233 | if typ != TYPE_NUMBER || cookie == TYPE_NUMBER_REAL { 234 | return nil, fmt.Errorf("unsupported table cookie: %v", head) 235 | } 236 | v, err := deserilizeInteger(buf, cookie) 237 | if err != nil { 238 | return nil, err 239 | } 240 | arrSize = v.(Integer) 241 | } else { 242 | arrSize = Integer(arrLen) 243 | } 244 | table := Table{ 245 | Array: make([]Value, arrSize), 246 | Hash: make(map[Value]Value), 247 | } 248 | for i := 0; i < int(arrSize); i++ { 249 | v, err := deserilizeLuaValue(buf) 250 | if err != nil { 251 | return nil, err 252 | } 253 | table.Array[i] = v 254 | } 255 | for { 256 | key, err := deserilizeLuaValue(buf) 257 | if err != nil { 258 | return nil, err 259 | } 260 | if key.LuaType() == LUA_NIL { 261 | break 262 | } 263 | value, err := deserilizeLuaValue(buf) 264 | if err != nil { 265 | return nil, err 266 | } 267 | table.Hash[key] = value 268 | 269 | } 270 | return table, nil 271 | } 272 | 273 | func deserilizeLongString(buf *bytes.Buffer, cookie uint8) (Value, error) { 274 | if cookie == 2 { 275 | var sz uint16 276 | err := binary.Read(buf, binary.LittleEndian, &sz) 277 | if err != nil { 278 | return nil, err 279 | } 280 | b := make([]byte, sz) 281 | _, err = buf.Read(b) 282 | return String(string(b)), err 283 | } 284 | if cookie == 4 { 285 | var sz uint32 286 | err := binary.Read(buf, binary.LittleEndian, &sz) 287 | if err != nil { 288 | return nil, err 289 | } 290 | b := make([]byte, sz) 291 | _, err = buf.Read(b) 292 | return String(string(b)), err 293 | } 294 | return nil, fmt.Errorf("unsupported long string cookie: %v", cookie) 295 | } 296 | 297 | func deserilizeReal(buf *bytes.Buffer) (Value, error) { 298 | var v float64 299 | err := binary.Read(buf, binary.LittleEndian, &v) 300 | return Real(v), err 301 | } 302 | 303 | func deserilizeInteger(buf *bytes.Buffer, cookie uint8) (Value, error) { 304 | switch cookie { 305 | case TYPE_NUMBER_ZERO: 306 | return Integer(0), nil 307 | case TYPE_NUMBER_BYTE: 308 | var v uint8 309 | err := binary.Read(buf, binary.LittleEndian, &v) 310 | return Integer(v), err 311 | case TYPE_NUMBER_WORD: 312 | var v uint16 313 | err := binary.Read(buf, binary.LittleEndian, &v) 314 | return Integer(v), err 315 | case TYPE_NUMBER_DWORD: 316 | var v int32 317 | err := binary.Read(buf, binary.LittleEndian, &v) 318 | return Integer(v), err 319 | case TYPE_NUMBER_QWORD: 320 | var v int64 321 | err := binary.Read(buf, binary.LittleEndian, &v) 322 | return Integer(v), err 323 | default: 324 | return nil, fmt.Errorf("unsupported integer cookie: %v", cookie) 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /lua/seri_test.go: -------------------------------------------------------------------------------- 1 | package lua 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestSerilizeIntegerZero(t *testing.T) { 9 | buf := bytes.NewBuffer(nil) 10 | serilizeInteger(buf, 0) 11 | packed := buf.Bytes() 12 | unpacked, err := Deserialize(packed) 13 | if err != nil { 14 | t.Errorf("deserialize failed: %v", err) 15 | } 16 | if unpacked[0] != Integer(0) { 17 | t.Errorf("serilize integer failed") 18 | } 19 | } 20 | 21 | func TestSerilizeIntegerBig(t *testing.T) { 22 | buf := bytes.NewBuffer(nil) 23 | serilizeInteger(buf, 0x7FFFFFFFFF) 24 | packed := buf.Bytes() 25 | unpacked, err := Deserialize(packed) 26 | if err != nil { 27 | t.Errorf("deserialize failed: %v", err) 28 | } 29 | if unpacked[0] != Integer(0x7FFFFFFFFF) { 30 | t.Errorf("serilize integer failed") 31 | } 32 | } 33 | 34 | func TestSerilizeNil(t *testing.T) { 35 | buf := bytes.NewBuffer(nil) 36 | serilizeNil(buf) 37 | packed := buf.Bytes() 38 | unpacked, err := Deserialize(packed) 39 | if err != nil { 40 | t.Errorf("deserialize failed: %v", err) 41 | } 42 | if unpacked[0].LuaType() != LUA_NIL { 43 | t.Errorf("serilize nil failed") 44 | } 45 | } 46 | 47 | func TestSerilizeBoolean(t *testing.T) { 48 | buf := bytes.NewBuffer(nil) 49 | serilizeBoolean(buf, true) 50 | packed := buf.Bytes() 51 | unpacked, err := Deserialize(packed) 52 | if err != nil { 53 | t.Errorf("deserialize failed: %v", err) 54 | } 55 | if unpacked[0] != Boolean(true) { 56 | t.Errorf("serilize boolean failed") 57 | } 58 | buf = bytes.NewBuffer(nil) 59 | serilizeBoolean(buf, false) 60 | packed = buf.Bytes() 61 | unpacked, err = Deserialize(packed) 62 | if err != nil { 63 | t.Errorf("deserialize failed: %v", err) 64 | } 65 | if unpacked[0] != Boolean(false) { 66 | t.Errorf("serilize boolean failed") 67 | } 68 | } 69 | 70 | func TestSerilizeReal(t *testing.T) { 71 | buf := bytes.NewBuffer(nil) 72 | serilizeReal(buf, 3.1415926) 73 | packed := buf.Bytes() 74 | unpacked, err := Deserialize(packed) 75 | if err != nil { 76 | t.Errorf("deserialize failed: %v", err) 77 | } 78 | if unpacked[0] != Real(3.1415926) { 79 | t.Errorf("serilize real failed") 80 | } 81 | } 82 | 83 | func TestSerilizeShortString(t *testing.T) { 84 | buf := bytes.NewBuffer(nil) 85 | serilizeString(buf, "hello") 86 | packed := buf.Bytes() 87 | unpacked, err := Deserialize(packed) 88 | if err != nil { 89 | t.Errorf("deserialize failed: %v", err) 90 | } 91 | if unpacked[0] != String("hello") { 92 | t.Errorf("serilize short string failed") 93 | } 94 | } 95 | 96 | func TestSerilizeLongString(t *testing.T) { 97 | buf := bytes.NewBuffer(nil) 98 | serilizeString(buf, "hellohellohellohellohellohellohellohello") 99 | packed := buf.Bytes() 100 | unpacked, err := Deserialize(packed) 101 | if err != nil { 102 | t.Errorf("deserialize failed: %v", err) 103 | } 104 | if unpacked[0] != String("hellohellohellohellohellohellohellohello") { 105 | t.Errorf("serilize long string failed") 106 | } 107 | } 108 | 109 | func TestSerilizeTableArray(t *testing.T) { 110 | buf := bytes.NewBuffer(nil) 111 | arr := Table{ 112 | Array: []Value{ 113 | Integer(1000000001), 114 | String("username"), 115 | Real(3.1415926), 116 | }, 117 | } 118 | err := serilizeTable(buf, arr, 0) 119 | if err != nil { 120 | t.Errorf("serilize table failed: %v", err) 121 | } 122 | packed := buf.Bytes() 123 | unpacked, err := Deserialize(packed) 124 | if err != nil { 125 | t.Errorf("deserialize failed: %v", err) 126 | } 127 | unpackedTable := unpacked[0].(Table) 128 | for i, v := range arr.Array { 129 | if v != unpackedTable.Array[i] { 130 | t.Errorf("value not match: %v != %v", v, unpackedTable.Array[i]) 131 | } 132 | } 133 | } 134 | 135 | func TestSerilizeTableHash(t *testing.T) { 136 | buf := bytes.NewBuffer(nil) 137 | table := Table{ 138 | Hash: map[Value]Value{ 139 | Integer(1000000001): String("uid"), 140 | String("title"): Integer(55), 141 | String("isOK"): Boolean(true), 142 | String("msg"): String("hello world"), 143 | }, 144 | } 145 | err := serilizeTable(buf, table, 0) 146 | if err != nil { 147 | t.Errorf("serilize table failed: %v", err) 148 | } 149 | packed := buf.Bytes() 150 | unpacked, err := Deserialize(packed) 151 | if err != nil { 152 | t.Errorf("deserialize failed: %v", err) 153 | } 154 | unpackedTable := unpacked[0].(Table) 155 | for k, v := range table.Hash { 156 | unpackedTableValue, ok := unpackedTable.Hash[k] 157 | if !ok { 158 | t.Errorf("key not found: %v", k) 159 | } 160 | if v != unpackedTableValue { 161 | t.Errorf("value not match: %v != %v", v, unpackedTableValue) 162 | } 163 | } 164 | } 165 | 166 | func TestSerilizeMixedTable(t *testing.T) { 167 | tableMixed := Table{ 168 | Array: []Value{ 169 | Integer(1000000001), 170 | String("username"), 171 | Real(3.1415926), 172 | }, 173 | Hash: map[Value]Value{ 174 | Integer(1000000001): String("uid"), 175 | String("title"): Integer(55), 176 | String("isOK"): Boolean(true), 177 | String("msg"): String("hello world"), 178 | }, 179 | } 180 | buf := bytes.NewBuffer(nil) 181 | err := serilizeTable(buf, tableMixed, 0) 182 | if err != nil { 183 | t.Errorf("serilize table failed: %v", err) 184 | } 185 | packed := buf.Bytes() 186 | unpacked, err := Deserialize(packed) 187 | if err != nil { 188 | t.Errorf("deserialize failed: %v", err) 189 | } 190 | unpackedTable := unpacked[0].(Table) 191 | for i, v := range tableMixed.Array { 192 | if v != unpackedTable.Array[i] { 193 | t.Errorf("value not match: %v != %v", v, unpackedTable.Array[i]) 194 | } 195 | } 196 | for k, v := range tableMixed.Hash { 197 | unpackedTableValue, ok := unpackedTable.Hash[k] 198 | if !ok { 199 | t.Errorf("key not found: %v", k) 200 | } 201 | if v != unpackedTableValue { 202 | t.Errorf("value not match: %v != %v", v, unpackedTableValue) 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /lua/value.go: -------------------------------------------------------------------------------- 1 | package lua 2 | 3 | type Value interface { 4 | LuaType() uint8 5 | } 6 | 7 | type Nil struct{} 8 | 9 | func (v Nil) LuaType() uint8 { 10 | return LUA_NIL 11 | } 12 | 13 | type Boolean bool 14 | 15 | func (v Boolean) LuaType() uint8 { 16 | return LUA_BOOLEAN 17 | } 18 | 19 | type Integer int64 20 | 21 | func (v Integer) LuaType() uint8 { 22 | return LUA_INTEGER 23 | } 24 | 25 | type Real float64 26 | 27 | func (v Real) LuaType() uint8 { 28 | return LUA_REAL 29 | } 30 | 31 | type String string 32 | 33 | func (v String) LuaType() uint8 { 34 | return LUA_STRING 35 | } 36 | 37 | type Table struct { 38 | Array []Value 39 | Hash map[Value]Value 40 | } 41 | 42 | func (v Table) LuaType() uint8 { 43 | return LUA_TABLE 44 | } 45 | -------------------------------------------------------------------------------- /moon.lua: -------------------------------------------------------------------------------- 1 | local skynet = require("skynet") 2 | local cluster = require("skynet.cluster") 3 | 4 | ---@class HttpOpts 5 | ---@field method string 6 | ---@field headers table | nil 7 | ---@field body string | nil 8 | ---@field noBody boolean | nil 9 | ---@field noHeader boolean | nil 10 | 11 | ---@class HttpResponse 12 | ---@field headers table 13 | ---@field body string 14 | 15 | ---comment 16 | ---@param url string 17 | ---@param opts HttpOpts 18 | ---@return boolean ok 19 | ---@return number code 20 | ---@return HttpResponse | nil response 21 | local function moonHttp(url, opts) 22 | opts = opts or {} 23 | opts.method = opts.method or "GET" 24 | opts.headers = opts.headers or {} 25 | opts.body = opts.body or "" 26 | 27 | opts.noBody = opts.noBody or false 28 | if opts.noHeader == nil then 29 | opts.noHeader = true 30 | end 31 | local ok, msg, code, resp = pcall(cluster.call, "moon", "http", "request", url, opts) 32 | if ok then 33 | return msg, code, resp 34 | else 35 | return false, msg 36 | end 37 | end 38 | 39 | skynet.start(function() 40 | cluster.reload({ 41 | moon = "127.0.0.1:3345", 42 | }) 43 | 44 | local ok, code, resp = moonHttp("https://www..com", { method = "GET" }) 45 | 46 | if ok and resp then 47 | skynet.error(resp.body) 48 | else 49 | skynet.error(code) 50 | end 51 | end) 52 | -------------------------------------------------------------------------------- /service/example.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Zwlin98/moon/lua" 7 | ) 8 | 9 | type ExampleService struct{} 10 | 11 | func NewExampleService() Service { 12 | return &ExampleService{} 13 | } 14 | 15 | func (s *ExampleService) Execute(args []lua.Value) ([]lua.Value, error) { 16 | fmt.Printf("ExampleService.Execute called with args: %v\n", args) 17 | 18 | return []lua.Value{ 19 | lua.Boolean(true), 20 | lua.String("hello world"), 21 | lua.Table{ 22 | Array: []lua.Value{ 23 | lua.Integer(1), 24 | lua.Real(3.14), 25 | lua.String("hello"), 26 | lua.Boolean(true), 27 | }, 28 | Hash: map[lua.Value]lua.Value{ 29 | lua.Boolean(true): lua.String("true"), 30 | lua.Boolean(false): lua.String("false"), 31 | lua.Integer(100): lua.String("hello world"), 32 | lua.String("number"): lua.Integer(200), 33 | lua.String("string"): lua.Boolean(true), 34 | lua.String("msg"): lua.String("hello world"), 35 | }, 36 | }, 37 | }, nil 38 | } 39 | -------------------------------------------------------------------------------- /service/http.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "log/slog" 8 | "net/http" 9 | "strings" 10 | "time" 11 | 12 | "github.com/Zwlin98/moon/lua" 13 | ) 14 | 15 | type request struct { 16 | url string 17 | method string 18 | headers map[string]string 19 | body string 20 | noBody bool 21 | noHeader bool 22 | } 23 | 24 | type HttpService struct { 25 | client *http.Client 26 | } 27 | 28 | func NewHttpService() Service { 29 | return &HttpService{ 30 | client: &http.Client{ 31 | Timeout: 10 * time.Second, 32 | Transport: &http.Transport{ 33 | MaxIdleConns: 128, 34 | MaxIdleConnsPerHost: 64, 35 | IdleConnTimeout: 90 * time.Second, 36 | }, 37 | }, 38 | } 39 | } 40 | 41 | func checkMethod(method string) bool { 42 | switch method { 43 | case "GET", "POST", "PUT", "DELETE", "PATCH": 44 | return true 45 | default: 46 | return false 47 | } 48 | } 49 | 50 | func parseArgs(args []lua.Value) (request, error) { 51 | var req request 52 | // 参数检查和处理 53 | if len(args) < 3 { 54 | return req, fmt.Errorf("args length error") 55 | } 56 | cmd, ok := args[0].(lua.String) 57 | if !ok || cmd != "request" { 58 | return req, fmt.Errorf("cmd parse error") 59 | } 60 | 61 | url, ok := args[1].(lua.String) 62 | if !ok { 63 | return req, fmt.Errorf("url parse error") 64 | } 65 | 66 | opts, ok := args[2].(lua.Table) 67 | if !ok { 68 | return req, fmt.Errorf("opts parse error") 69 | } 70 | 71 | method := opts.Hash[lua.String("method")].(lua.String) 72 | headers := opts.Hash[lua.String("headers")].(lua.Table) 73 | body := opts.Hash[lua.String("body")].(lua.String) 74 | noBody := opts.Hash[lua.String("noBody")].(lua.Boolean) 75 | noHeader := opts.Hash[lua.String("noHeader")].(lua.Boolean) 76 | 77 | if !checkMethod(string(method)) { 78 | return req, fmt.Errorf("method error") 79 | } 80 | 81 | reqHeaders := make(map[string]string) 82 | for k, v := range headers.Hash { 83 | reqHeaders[string(k.(lua.String))] = string(v.(lua.String)) 84 | } 85 | 86 | return request{ 87 | url: string(url), 88 | method: string(method), 89 | headers: reqHeaders, 90 | body: string(body), 91 | noBody: bool(noBody), 92 | noHeader: bool(noHeader), 93 | }, nil 94 | 95 | } 96 | 97 | func (s *HttpService) Execute(args []lua.Value) (ret []lua.Value, err error) { 98 | defer func() { 99 | if err := recover(); err != nil { 100 | slog.Warn("http service panic", "err", err) 101 | ret = buildError(fmt.Sprintf("http service panic: %v", err)) 102 | err = nil 103 | } 104 | }() 105 | 106 | req, err := parseArgs(args) 107 | if err != nil { 108 | return buildError(err.Error()), nil 109 | } 110 | 111 | return s.httpRequest(req) 112 | } 113 | 114 | func (s *HttpService) httpRequest(req request) ([]lua.Value, error) { 115 | r, err := http.NewRequest(req.method, req.url, strings.NewReader(req.body)) 116 | if err != nil { 117 | return buildError(err.Error()), nil 118 | } 119 | for k, v := range req.headers { 120 | r.Header.Set(k, v) 121 | } 122 | resp, err := s.client.Do(r) 123 | if err != nil { 124 | return buildError(err.Error()), nil 125 | } 126 | return buildResponse(resp, req.noHeader, req.noBody), nil 127 | } 128 | 129 | func buildResponse(resp *http.Response, noHeader bool, noBody bool) []lua.Value { 130 | 131 | statusCode := lua.Integer(resp.StatusCode) 132 | 133 | ret := make([]lua.Value, 0, 4) 134 | ret = append(ret, lua.Boolean(true)) 135 | ret = append(ret, statusCode) 136 | 137 | hash := make(map[lua.Value]lua.Value) 138 | if !noHeader { 139 | headers := make(map[lua.Value]lua.Value) 140 | for k, v := range resp.Header { 141 | if len(v) == 1 { 142 | headers[lua.String(k)] = lua.String(v[0]) 143 | } else { 144 | arr := make([]lua.Value, 0, len(v)) 145 | for _, vv := range v { 146 | arr = append(arr, lua.String(vv)) 147 | } 148 | headers[lua.String(k)] = lua.Table{ 149 | Array: arr, 150 | } 151 | } 152 | } 153 | hash[lua.String("headers")] = lua.Table{ 154 | Hash: headers, 155 | } 156 | } 157 | 158 | if !noBody { 159 | defer resp.Body.Close() 160 | buf := bytes.NewBuffer(nil) 161 | io.Copy(buf, resp.Body) 162 | hash[lua.String("body")] = lua.String(buf.String()) 163 | } else { 164 | io.Copy(io.Discard, resp.Body) 165 | resp.Body.Close() 166 | } 167 | 168 | ret = append(ret, lua.Table{Hash: hash}) 169 | 170 | return ret 171 | } 172 | 173 | func buildError(msg string) []lua.Value { 174 | return []lua.Value{ 175 | lua.Boolean(false), 176 | lua.String(msg), 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /service/ping.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "log/slog" 6 | 7 | "github.com/Zwlin98/moon/lua" 8 | ) 9 | 10 | type PingService struct{} 11 | 12 | func NewPingService() Service { 13 | return &PingService{} 14 | } 15 | 16 | func (s *PingService) Execute(args []lua.Value) (ret []lua.Value, err error) { 17 | defer func() { 18 | if err := recover(); err != nil { 19 | slog.Warn("ping service panic", "err", err) 20 | ret = []lua.Value{lua.String("error panic")} 21 | err = fmt.Errorf("panic: %v", err) 22 | } 23 | }() 24 | 25 | if len(args) < 1 { 26 | return []lua.Value{lua.String("error args")}, nil 27 | } 28 | 29 | method, ok := args[0].(lua.String) 30 | if !ok { 31 | return []lua.Value{lua.String("error args")}, nil 32 | } 33 | 34 | if method == "ping" { 35 | return []lua.Value{lua.String("pong")}, nil 36 | } 37 | 38 | return []lua.Value{lua.String("error method")}, nil 39 | } 40 | -------------------------------------------------------------------------------- /service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/Zwlin98/moon/lua" 5 | ) 6 | 7 | type Service interface { 8 | Execute([]lua.Value) ([]lua.Value, error) 9 | } 10 | 11 | type LuaFunction func([]lua.Value) ([]lua.Value, error) 12 | --------------------------------------------------------------------------------