├── .gitignore ├── .gitmodules ├── web-server ├── web_start.lua ├── websocket_router.lua ├── config ├── system_monitor.lua ├── web_logger.lua ├── taskinfo.lua ├── router.lua ├── websocket_service.lua └── web_service.lua ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | web-server/test_taskinfo.lua 2 | packages/ 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "packages/skynet"] 2 | path = packages/skynet 3 | url = https://github.com/cloudwu/skynet 4 | [submodule "packages/lua-cjson"] 5 | path = packages/lua-cjson 6 | url = https://github.com/mpx/lua-cjson/ 7 | -------------------------------------------------------------------------------- /web-server/web_start.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | skynet.start(function () 4 | skynet.error("Server start") 5 | skynet.uniqueservice("system_monitor") 6 | skynet.newservice("debug_console",8000) 7 | skynet.newservice("web_service") 8 | skynet.newservice("websocket_service") 9 | 10 | skynet.exit() 11 | end) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # skynet-admin 2 | 3 | 该 demo 需要配合 web 端([skynet-admin-web](https://github.com/CenWuCN/skynet-admin-web))使用 4 | 5 | skynet-admin 和 skynet-admin-web 打包好的 docker 镜像 [skynet-admin-docker](https://github.com/CenWuCN/skynet-admin-docker) 6 | 7 | ## 运行 8 | 9 | ```bash 10 | git clone https://github.com/CenWuCN/skynet-admin 11 | cd skynet-admin 12 | git submodule init 13 | git submodule update 14 | cd packages/skynet 15 | make linux 16 | cd ../lua-cjson 17 | make 18 | cp cjson.so ../skynet/luaclib 19 | cd ../skynet 20 | ./skynet ../../web-server/config 21 | ``` 22 | 23 | 即可启动服务端 -------------------------------------------------------------------------------- /web-server/websocket_router.lua: -------------------------------------------------------------------------------- 1 | local cjson = require "cjson" 2 | 3 | local function log(text) 4 | return text 5 | end 6 | local function cpu(data) 7 | local text = cjson.encode(data) 8 | return text 9 | end 10 | 11 | local url_func = { 12 | ["/debug/log"] = { func = log, id_map = {} }, 13 | ["/debug/cpu"] = { func = cpu, id_map = {} } 14 | } 15 | 16 | local websocket_router = {} 17 | 18 | function websocket_router.get_url_func(url) 19 | return url_func[url] 20 | end 21 | 22 | function websocket_router.set_id(url, id) 23 | local map = url_func[url] 24 | if map and map.id_map then 25 | map.id_map[id] = true 26 | end 27 | end 28 | 29 | function websocket_router.remove_id(url, id) 30 | local map = url_func[url] 31 | if map and map.id_map then 32 | map.id_map[id] = nil 33 | end 34 | end 35 | 36 | return websocket_router -------------------------------------------------------------------------------- /web-server/config: -------------------------------------------------------------------------------- 1 | -- preload = "./examples/preload.lua" -- run preload.lua before every lua service run 2 | thread = 8 3 | logger = "web_logger" 4 | logpath = "." 5 | logservice = "snlua" 6 | harbor = 1 7 | address = "127.0.0.1:2526" 8 | master = "127.0.0.1:2013" 9 | start = "web_start" -- main script 10 | bootstrap = "snlua bootstrap" -- The service for bootstrap 11 | standalone = "0.0.0.0:2013" 12 | root = "./" 13 | -- snax_interface_g = "snax_g" 14 | cpath = root.."cservice/?.so" 15 | -- daemon = "./skynet.pid" 16 | luaservice = root.."service/?.lua;"..root.."test/?.lua;"..root.."examples/?.lua;"..root.."test/?/init.lua;"..root.."../../web-server/?.lua" 17 | lualoader = root .. "lualib/loader.lua" 18 | lua_path = root.."lualib/?.lua;"..root.."lualib/?/init.lua;"..root.."../../web-server/?.lua" 19 | lua_cpath = root .. "luaclib/?.so" 20 | snax = root.."examples/?.lua;"..root.."test/?.lua" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022-2025 CenWuCN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /web-server/system_monitor.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local mc = require "skynet.multicast" 3 | require "skynet.manager" 4 | local taskinfo = require "taskinfo" 5 | local tinsert = table.insert 6 | local tremove = table.remove 7 | 8 | local info_list = {} 9 | local info_list_max_len = 120 10 | 11 | local Monitor = {} 12 | local channel = nil 13 | local cpu = "cpu" 14 | 15 | local function add_info(info) 16 | tinsert(info_list, info) 17 | if #info_list > info_list_max_len then 18 | tremove(info_list, 1) 19 | end 20 | end 21 | 22 | local function main_loop() 23 | local output = taskinfo.get_proccess_info() 24 | local info = taskinfo.parse_info(output) 25 | add_info(info) 26 | channel:publish(cpu, info) 27 | skynet.timeout(500, main_loop) 28 | end 29 | 30 | function Monitor.getinfolist() 31 | return info_list 32 | end 33 | 34 | function Monitor.gethostinfo() 35 | return taskinfo.get_host_info() 36 | end 37 | 38 | skynet.start(function() 39 | taskinfo.init() 40 | skynet.dispatch("lua", function(_, _, cmd, ...) 41 | local f = assert(Monitor[cmd]) 42 | skynet.ret(skynet.pack(f(...))) 43 | end) 44 | channel = mc.new() 45 | skynet.setenv("websocket_channel", channel.channel) 46 | skynet.send(".Web_logger", "lua", "set_channel") 47 | skynet.timeout(0, main_loop) 48 | skynet.register(".SystemMonitor") 49 | end) -------------------------------------------------------------------------------- /web-server/web_logger.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local io = require "io" 3 | local osdate = os.date 4 | require "skynet.manager" 5 | 6 | local channel = nil 7 | local log = "log" 8 | local file = io.open("weblog", "w+") 9 | if not file then 10 | print("log file weblog open failed" .. msg) 11 | return 12 | end 13 | 14 | local COMMAND = {} 15 | function COMMAND.set_channel() 16 | local mc = require "skynet.multicast" 17 | local id = skynet.getenv("websocket_channel") 18 | channel = mc.new({ 19 | channel = tonumber(id) 20 | }) 21 | end 22 | 23 | -- register protocol text before skynet.start would be better. 24 | skynet.register_protocol { 25 | name = "text", 26 | id = skynet.PTYPE_TEXT, 27 | unpack = skynet.tostring, 28 | dispatch = function(_, address, msg) 29 | local logtext = string.format(":%08x(%s): %s", address, osdate("%Y%m%d-%H:%M:%S"), msg) 30 | print(logtext) 31 | local logtext = logtext .. "\n" 32 | if channel then 33 | channel:publish(log, logtext) 34 | end 35 | if file then 36 | file:write(logtext) 37 | file:flush() 38 | end 39 | end 40 | } 41 | 42 | skynet.register_protocol { 43 | name = "SYSTEM", 44 | id = skynet.PTYPE_SYSTEM, 45 | unpack = function(...) return ... end, 46 | dispatch = function() 47 | -- reopen signal 48 | print("SIGHUP") 49 | end 50 | } 51 | 52 | skynet.start(function() 53 | skynet.dispatch("lua", function(_, _, cmd, ...) 54 | local f = assert(COMMAND[cmd]) 55 | f(...) 56 | end) 57 | skynet.register(".Web_logger") 58 | end) -------------------------------------------------------------------------------- /web-server/taskinfo.lua: -------------------------------------------------------------------------------- 1 | local io = require "io" 2 | local smatch = string.match 3 | local tinsert = table.insert 4 | 5 | local taskinfo = {} 6 | local command = "top -bn1 -p 0" 7 | local total_mem = nil 8 | local host_info = {} 9 | 10 | local cpu_id_reg = "([0-9%.]+)% id" 11 | local mem_total_reg = "Mem[:% ]+([0-9%.]+)% total" 12 | local mem_unit_reg = "([a-zA-Z]+)% Mem" 13 | local mem_used_reg = "([0-9%.]+)% used" 14 | local cpu_model_reg = "model name[%s]*:[%s]*([%g% ]*)" 15 | 16 | function taskinfo.init() 17 | local output = taskinfo.get_proccess_info() 18 | total_mem = tonumber(smatch(output, mem_total_reg)) 19 | local unit = smatch(output, mem_unit_reg) 20 | host_info.total_mem = total_mem .. " ".. unit 21 | taskinfo.cpu_model() 22 | end 23 | 24 | function taskinfo.cpu_model() 25 | local file = io.popen("cat /proc/cpuinfo") 26 | local output = file:read("a") 27 | file:close() 28 | host_info.cpu_model = smatch(output, cpu_model_reg) 29 | end 30 | 31 | function taskinfo.get_proccess_info() 32 | local file = io.popen(command) 33 | local output = file:read("a") 34 | file:close() 35 | return output 36 | end 37 | 38 | function taskinfo.parse_info(output) 39 | local info = {} 40 | tinsert(info, os.time() * 1000) 41 | tinsert(info, 100 - tonumber(smatch(output, cpu_id_reg))) 42 | tinsert(info, tonumber(smatch(output, mem_used_reg))/total_mem * 10000//1/100) 43 | return info 44 | end 45 | 46 | function taskinfo.get_host_info() 47 | return host_info 48 | end 49 | 50 | return taskinfo -------------------------------------------------------------------------------- /web-server/router.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local tinsert = table.insert 3 | local smatch = string.match 4 | local sgmatch = string.gmatch 5 | 6 | local function timeout(ti) 7 | if ti then 8 | ti = tonumber(ti) 9 | if ti <= 0 then 10 | ti = nil 11 | end 12 | else 13 | ti = TIMEOUT 14 | end 15 | return ti 16 | end 17 | 18 | local function list() 19 | return skynet.call(".launcher", "lua", "LIST") 20 | end 21 | 22 | local function gc() 23 | return skynet.call(".launcher", "lua", "GC", timeout(ti)) 24 | end 25 | 26 | local mem_reg = "([0-9%.]+% [a-zA-Z]+)" 27 | local name_args_reg = "%((.+)%)" 28 | 29 | local function parse_mem(data) 30 | local mem_info = {} 31 | mem_info.mem = smatch(data, mem_reg) 32 | mem_info.args = "" 33 | local name_args = smatch(data, name_args_reg) 34 | local index = 0 35 | for value in sgmatch(name_args, "[^%s]+") do 36 | index = index + 1 37 | if index == 1 then 38 | mem_info.type = value 39 | elseif index == 2 then 40 | mem_info.service = value 41 | else 42 | if index > 3 then 43 | mem_info.args = mem_info.args .. " " 44 | end 45 | mem_info.args = mem_info.args .. value 46 | end 47 | end 48 | return mem_info 49 | end 50 | 51 | local function mem() 52 | local mem_list = skynet.call(".launcher", "lua", "MEM", timeout(ti)) 53 | local transfrom = {} 54 | for addr, data in pairs(mem_list) do 55 | local mem_info = parse_mem(data) 56 | mem_info.addr = addr 57 | tinsert(transfrom, mem_info) 58 | end 59 | return transfrom 60 | end 61 | 62 | local function stat() 63 | local stat_list = skynet.call(".launcher", "lua", "STAT", timeout(ti)) 64 | local transfrom = {} 65 | for addr, data in pairs(stat_list) do 66 | tinsert(transfrom, { 67 | addr = addr, 68 | task = data.task, 69 | message = data.message, 70 | cpu = data.cpu, 71 | mqlen = data.mqlen 72 | }) 73 | end 74 | return transfrom 75 | end 76 | 77 | local function service() 78 | return skynet.call("SERVICE", "lua", "LIST") 79 | end 80 | 81 | local function infolist() 82 | return skynet.call(".SystemMonitor", "lua", "getinfolist") 83 | end 84 | 85 | local function hostinfo() 86 | return skynet.call(".SystemMonitor", "lua", "gethostinfo") 87 | end 88 | 89 | local function sendmessage() 90 | skynet.error("print one line log") 91 | end 92 | 93 | local router = { 94 | ["/debug/list"] = list, 95 | ["/debug/gc"] = gc, 96 | ["/debug/mem"] = mem, 97 | ["/debug/stat"] = stat, 98 | ["/debug/service"] = service, 99 | ["/debug/infolist"] = infolist, 100 | ["/debug/hostinfo"] = hostinfo, 101 | ["/debug/sendmessage"] = sendmessage 102 | } 103 | 104 | return router -------------------------------------------------------------------------------- /web-server/websocket_service.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local socket = require "skynet.socket" 3 | local service = require "skynet.service" 4 | local websocket = require "http.websocket" 5 | local mc = require "skynet.multicast" 6 | require "skynet.manager" 7 | 8 | local handle = {} 9 | local MODE = ... 10 | 11 | if MODE == "agent" then 12 | local ConnectedAgentId = {} 13 | local ConnectedAgentCount = 0 14 | local websocket_router = require "websocket_router" 15 | local COMMAND = { 16 | ["log"] = "/debug/log", 17 | ["cpu"] = "/debug/cpu" 18 | } 19 | local channel = nil 20 | 21 | function handle.connect(id) 22 | print("ws connect from: " .. tostring(id)) 23 | end 24 | 25 | function handle.handshake(id, header, url) 26 | local addr = websocket.addrinfo(id) 27 | websocket_router.set_id(url, id) 28 | print("ws handshake from: " .. tostring(id), "url", url, "addr:", addr) 29 | print("----header-----") 30 | for k,v in pairs(header) do 31 | print(k,v) 32 | end 33 | print("--------------") 34 | end 35 | 36 | function handle.message(id, msg, msg_type) 37 | assert(msg_type == "binary" or msg_type == "text") 38 | websocket.write(id, msg) 39 | end 40 | 41 | function handle.ping(id) 42 | print("ws ping from: " .. tostring(id) .. "\n") 43 | end 44 | 45 | function handle.pong(id) 46 | print("ws pong from: " .. tostring(id)) 47 | end 48 | 49 | function handle.close(id, code, reason) 50 | websocket_router.remove_id(id) 51 | end 52 | 53 | function handle.error(id) 54 | websocket_router.remove_id(id) 55 | end 56 | 57 | skynet.start(function () 58 | skynet.dispatch("lua", function (_,_, id, protocol, addr) 59 | local ok, err = websocket.accept(id, handle, protocol, addr) 60 | if not ok then 61 | print(err) 62 | end 63 | end) 64 | local channelid = skynet.getenv("websocket_channel") 65 | channel = mc.new({ 66 | channel = tonumber(channelid), 67 | dispatch = function(channel, source, cmd, data) 68 | local url = assert(COMMAND[cmd]) 69 | local url_func = websocket_router.get_url_func(url) 70 | if url_func then 71 | for id in pairs(url_func.id_map) do 72 | local ret, msg = pcall(websocket.write, id, url_func.func(data)) 73 | if not ret then 74 | websocket_router.remove_id(url, id) 75 | skynet.error(id .. msg) 76 | end 77 | end 78 | end 79 | end 80 | }) 81 | channel:subscribe() 82 | end) 83 | else 84 | local agent = {} 85 | 86 | skynet.start(function () 87 | for i= 1, 1 do 88 | agent[i] = skynet.newservice(SERVICE_NAME, "agent") 89 | end 90 | local balance = 1 91 | local protocol = "ws" 92 | local id = socket.listen("0.0.0.0", 9948) 93 | skynet.error(string.format("Listen websocket port 9948 protocol:%s", protocol)) 94 | socket.start(id, function(id, addr) 95 | skynet.send(agent[balance], "lua", id, protocol, addr) 96 | balance = balance + 1 97 | if balance > #agent then 98 | balance = 1 99 | end 100 | end) 101 | end) 102 | end -------------------------------------------------------------------------------- /web-server/web_service.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local socket = require "skynet.socket" 3 | local httpd = require "http.httpd" 4 | local sockethelper = require "http.sockethelper" 5 | local urllib = require "http.url" 6 | local cjson = require "cjson" 7 | local router = require "router" 8 | local table = table 9 | local string = string 10 | 11 | local mode, protocol = ... 12 | protocol = protocol or "http" 13 | 14 | if mode == "agent" then 15 | 16 | local function response(id, write, ...) 17 | local ok, err = httpd.write_response(write, ...) 18 | if not ok then 19 | -- if err == sockethelper.socket_error , that means socket closed. 20 | skynet.error(string.format("fd = %d, %s", id, err)) 21 | end 22 | end 23 | 24 | 25 | local SSLCTX_SERVER = nil 26 | local function gen_interface(protocol, fd) 27 | if protocol == "http" then 28 | return { 29 | init = nil, 30 | close = nil, 31 | read = sockethelper.readfunc(fd), 32 | write = sockethelper.writefunc(fd), 33 | } 34 | elseif protocol == "https" then 35 | local tls = require "http.tlshelper" 36 | if not SSLCTX_SERVER then 37 | SSLCTX_SERVER = tls.newctx() 38 | -- gen cert and key 39 | -- openssl req -x509 -newkey rsa:2048 -days 3650 -nodes -keyout server-key.pem -out server-cert.pem 40 | local certfile = skynet.getenv("certfile") or "./server-cert.pem" 41 | local keyfile = skynet.getenv("keyfile") or "./server-key.pem" 42 | print(certfile, keyfile) 43 | SSLCTX_SERVER:set_cert(certfile, keyfile) 44 | end 45 | local tls_ctx = tls.newtls("server", SSLCTX_SERVER) 46 | return { 47 | init = tls.init_responsefunc(fd, tls_ctx), 48 | close = tls.closefunc(tls_ctx), 49 | read = tls.readfunc(fd, tls_ctx), 50 | write = tls.writefunc(fd, tls_ctx), 51 | } 52 | else 53 | error(string.format("Invalid protocol: %s", protocol)) 54 | end 55 | end 56 | 57 | skynet.start(function() 58 | skynet.dispatch("lua", function (_,_,id) 59 | socket.start(id) 60 | local interface = gen_interface(protocol, id) 61 | if interface.init then 62 | interface.init() 63 | end 64 | -- limit request body size to 8192 (you can pass nil to unlimit) 65 | local code, url, method, header, body = httpd.read_request(interface.read, 8192) 66 | print("code", code, "method", method, "url", url) 67 | if code then 68 | if code ~= 200 then 69 | response(id, interface.write, code) 70 | else 71 | local func = router[url] 72 | if not func then 73 | local tmp = {} 74 | if header.host then 75 | table.insert(tmp, string.format("host: %s", header.host)) 76 | end 77 | local path, query = urllib.parse(url) 78 | table.insert(tmp, string.format("path: %s", path)) 79 | if query then 80 | local q = urllib.parse_query(query) 81 | for k, v in pairs(q) do 82 | table.insert(tmp, string.format("query: %s= %s", k,v)) 83 | end 84 | end 85 | table.insert(tmp, "-----header----") 86 | for k,v in pairs(header) do 87 | table.insert(tmp, string.format("%s = %s",k,v)) 88 | end 89 | table.insert(tmp, "-----body----\n" .. body) 90 | table.insert(tmp, "404 FILE NOT FOUND\n") 91 | response(id, interface.write, code, table.concat(tmp,"\n")) 92 | else 93 | local ret = func() 94 | local tmp = {} 95 | if type(ret) == "table" then 96 | local cjsonText = cjson.encode(ret) 97 | table.insert(tmp, cjsonText) 98 | else 99 | table.insert(tmp, ret) 100 | end 101 | response(id, interface.write, code, table.concat(tmp,"\n")) 102 | end 103 | end 104 | else 105 | if url == sockethelper.socket_error then 106 | skynet.error("socket closed") 107 | else 108 | skynet.error(url) 109 | end 110 | end 111 | socket.close(id) 112 | if interface.close then 113 | interface.close() 114 | end 115 | end) 116 | end) 117 | 118 | else 119 | 120 | skynet.start(function() 121 | local agent = {} 122 | local protocol = "http" 123 | for i= 1, 1 do 124 | agent[i] = skynet.newservice(SERVICE_NAME, "agent", protocol) 125 | end 126 | local balance = 1 127 | local id = socket.listen("0.0.0.0", 8001) 128 | skynet.error(string.format("Listen web port 8001 protocol:%s", protocol)) 129 | socket.start(id , function(id, addr) 130 | skynet.error(string.format("%s connected, pass it to agent :%08x", addr, agent[balance])) 131 | skynet.send(agent[balance], "lua", id) 132 | balance = balance + 1 133 | if balance > #agent then 134 | balance = 1 135 | end 136 | end) 137 | end) 138 | 139 | end --------------------------------------------------------------------------------