--!A cross-platform build utility based on Lua -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- -- Copyright (C) 2015-present, Xmake Open Source Community. -- -- @author ruki -- @file server.lua -- -- imports import("core.base.object") import("core.base.bytes") import("core.base.base64") import("core.base.hashset") import("core.base.socket") import("core.base.scheduler") import("private.service.server_config", {alias = "config"}) import("private.service.message") import("private.service.stream", {alias = "socket_stream"}) -- define module local server = server or object() -- init server function server:init(daemon) self._DAEMON = daemon -- init authorizations local tokens = config.get("tokens") self:tokens_set(tokens) -- init known hosts local known_hosts = config.get("known_hosts") self:known_hosts_set(known_hosts) -- init timeout self._SEND_TIMEOUT = config.get("send_timeout") or -1 self._RECV_TIMEOUT = config.get("recv_timeout") or -1 end -- is daemon? function server:daemon() return self._DAEMON end -- set handler function server:handler_set(handler) self._HANDLER = handler end -- set the given listen address function server:address_set(address) local splitinfo = address:split(':', {plain = true}) if #splitinfo == 2 then self._ADDR = splitinfo[1] self._PORT = splitinfo[2] else self._ADDR = "127.0.0.1" self._PORT = splitinfo[1] end assert(self._ADDR and self._PORT, "invalid listen address!") end -- get the listen address function server:addr() return self._ADDR end -- get the listen port function server:port() return self._PORT end -- get send timeout function server:send_timeout() return self._SEND_TIMEOUT end -- get recv timeout function server:recv_timeout() return self._RECV_TIMEOUT end -- get tokens function server:tokens() return self._TOKENS end -- set tokens function server:tokens_set(tokens) self._TOKENS = tokens and hashset.from(tokens) or hashset.new() end -- get known hosts function server:known_hosts() return self._KNOWN_HOSTS end -- set known hosts function server:known_hosts_set(hosts) self._KNOWN_HOSTS = hosts and hashset.from(hosts) or hashset.new() end -- we need to verify user function server:need_verfiy() return not self:tokens():empty() end -- verify user function server:verify_user(token, peeraddr) if not token then return false, "client has no authorization, we need to add username to connect address or token!" end -- check authorization if not self:tokens():has(token) then return false, "user and password are incorrect!" end -- check known_hosts if not self:known_hosts():empty() and peeraddr then local addrinfo = peeraddr:split(":") if addrinfo and #addrinfo == 2 then local addr = addrinfo[1] if not self:known_hosts():has(addr) then return false, "your host address is unknown in server!" end end end return true end -- run main loop function server:runloop() assert(self._HANDLER, "no handler found!") -- ensure only one server process local lock = io.openlock(self:lockfile()) if not lock:trylock() then raise("%s: has been started!", self) end -- save the current pid for stopping service io.writefile(self:pidfile(), os.getpid()) -- run loop local sock = socket.bind(self:addr(), self:port()) sock:listen(100) print("%s: listening %s:%d ..", self, self:addr(), self:port()) io.flush() while true do local sock_client = sock:accept() if sock_client then scheduler.co_start(function (sock) self:_handle_session(sock) sock:close() end, sock_client) end end io.flush() sock:close() end -- get class function server:class() return server end -- get pid file function server:pidfile() return path.join(self:workdir(), "server.pid") end -- get lock file function server:lockfile() return path.join(self:workdir(), "server.lock") end -- get working directory function server:workdir() return os.tmpfile(tostring(self)) .. ".dir" end -- handle session function server:_handle_session(sock) print("%s: %s: session connected", self, sock) local stream = socket_stream(sock, {send_timeout = self:send_timeout(), recv_timeout = self:recv_timeout()}) while true do local msg = stream:recv_object({timeout = -1}) if msg then local ok = try { function () self:_HANDLER(stream, message(msg)) return true end, catch { function (errors) vprint(errors) end } } if not ok then break end else break end end print("%s: %s: session end", self, sock) end function server:__tostring() return "" end function main() local instance = server() instance:init() return instance end