Skip to content

Commit 2ea8f08

Browse files
committed
Refact feather to provide better pluggin support
1 parent 7a7c116 commit 2ea8f08

File tree

9 files changed

+301
-193
lines changed

9 files changed

+301
-193
lines changed

src-lua/feather/init.lua

Lines changed: 27 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,33 @@ local utf8 = require("utf8")
55
-- lib Path
66
local PATH = (...):gsub("%.init$", "")
77

8-
local inspect = require(PATH .. ".lib.inspect")
9-
local json = require(PATH .. ".lib.json")
108
local Class = require(PATH .. ".lib.class")
119
local errorhandler = require(PATH .. ".error_handler")
12-
local get_current_dir = require(PATH .. ".utils").get_current_dir
1310
local Performance = require(PATH .. ".plugins.performance")
1411
local FeatherPluginManager = require(PATH .. ".plugin_manager")
12+
local FeatherLogger = require(PATH .. ".plugins.logger")
13+
local get_current_dir = require(PATH .. ".utils").get_current_dir
14+
local format = require(PATH .. ".utils").format
15+
local serverUtils = require(PATH .. ".server_utils")
1516

1617
local performance = Performance()
1718

18-
local logs = {}
1919
local FEATHER_VERSION = "0.2.0"
2020

2121
---@class Feather: FeatherConfig
2222
---@field lastDelivery number
2323
---@field lastError number
2424
---@field debug boolean
25+
---@field featherLogger FeatherLogger
2526
---@field protected server any
2627
---@field observe fun(self: Feather, key: string, value: table | string | number | boolean) Updates value in the observers tab
27-
---@field log fun(self: Feather, line: FeatherLine) Logs a line
2828
---@field finish fun(self: Feather) Logs a finish line
2929
---@field print fun(self: Feather, ...) Prints a log
3030
---@field trace fun(self: Feather, ...) Prints a trace
3131
---@field error fun(self: Feather, msg: string) Prints an error
3232
---@field update fun(self: Feather, dt: number) Updates the Feather instance
33-
---@field protected __print fun(self: Feather, type: string, str: string)
34-
---@field protected __countOnRepeat fun(self: Feather, type: LogType, ...: unknown)
3533
---@field protected __onerror fun(self: Feather, msg: string, finish: boolean)
3634
---@field protected __getConfig fun(self: Feather): FeatherConfig
37-
---@field protected __buildResponse fun(self: Feather, body: table | string | number): string
38-
---@field protected __buildRequest fun(self: Feather, request: table): table
39-
---@field protected __format fun(self: Feather, ...: any): string
40-
---@field protected __isInWhitelist fun(self: Feather, addr: string): boolean
4135
---@field protected __defaultObservers fun(self: Feather)
4236
---@field protected __errorTraceback fun(self: Feather, msg: string): string
4337
local Feather = Class({})
@@ -92,6 +86,9 @@ function Feather:init(config)
9286
print("Listening on " .. self.addr .. ":" .. self.port)
9387
self.server:settimeout(0)
9488

89+
---@type FeatherLogger
90+
self.featherLogger = FeatherLogger(self)
91+
9592
if self.autoRegisterErrorHandler then
9693
local selfRef = self -- capture `self` to avoid upvalue issues
9794

@@ -115,34 +112,7 @@ function Feather:init(config)
115112
end
116113
end
117114

118-
-- Wrap print
119-
self.logger = print
120-
if self.wrapPrint then
121-
local logger = print
122-
123-
local selfRef = self -- capture `self` to avoid upvalue issues
124-
125-
--
126-
print = function(...)
127-
logger(...)
128-
selfRef.print(self, ...)
129-
end
130-
end
131-
132-
self.pluginManager = FeatherPluginManager(self, logs)
133-
end
134-
135-
function Feather:__buildResponse(body)
136-
local response = table.concat({
137-
"HTTP/1.1 200 OK",
138-
"Content-Type: application/json",
139-
"Access-Control-Allow-Origin: *",
140-
"Content-Length: " .. #body,
141-
"",
142-
body,
143-
}, "\r\n")
144-
145-
return response
115+
self.pluginManager = FeatherPluginManager(self, self.featherLogger)
146116
end
147117

148118
function Feather:__getConfig()
@@ -152,58 +122,15 @@ function Feather:__getConfig()
152122
root_path = root_path .. "/" .. self.baseDir
153123
end
154124

155-
local pluginKeys = {}
156-
157-
for _, plugin in ipairs(self.plugins) do
158-
table.insert(pluginKeys, plugin.identifier)
159-
end
160-
161125
local config = {
162-
plugins = pluginKeys,
126+
plugins = self.pluginManager:getConfig(),
163127
root_path = root_path,
164128
version = FEATHER_VERSION,
165129
}
166130

167131
return config
168132
end
169133

170-
--- Builds a request object from a raw request string
171-
---@param request string
172-
---@return table
173-
function Feather:__buildRequest(request)
174-
local method, pathWithQuery = request:match("^(%u+)%s+([^%s]+)")
175-
local path, queryString = pathWithQuery:match("^([^?]+)%??(.*)$")
176-
local function parseQuery(qs)
177-
local params = {}
178-
for key, val in qs:gmatch("([^&=?]+)=([^&=?]+)") do
179-
params[key] = val
180-
end
181-
return params
182-
end
183-
184-
local params = parseQuery(queryString)
185-
186-
return {
187-
method = method,
188-
path = path,
189-
params = params,
190-
}
191-
end
192-
193-
function Feather:__format(...)
194-
return inspect(..., { newline = "\n", indent = "\t" })
195-
end
196-
197-
function Feather:__isInWhitelist(addr)
198-
for _, a in pairs(self.whitelist) do
199-
local ptn = "^" .. a:gsub("%.", "%%."):gsub("%*", "%%d*") .. "$"
200-
if addr:match(ptn) then
201-
return true
202-
end
203-
end
204-
return false
205-
end
206-
207134
function Feather:__defaultObservers()
208135
self:observe("Global", _G)
209136
self:observe("Lua Version", _VERSION)
@@ -250,9 +177,9 @@ function Feather:__onerror(msg, finish)
250177
end
251178

252179
local err = self:__errorTraceback(msg)
253-
self:log({ type = "error", str = self:__errorTraceback(msg) })
180+
self.featherLogger:log({ type = "error", str = self:__errorTraceback(msg) })
254181
if self.wrapPrint then
255-
self.logger("[Feather] ERROR: " .. err)
182+
self.featherLogger.logger("[Feather] ERROR: " .. err)
256183
end
257184
self.lastError = os.time()
258185
self.pluginManager:onerror(msg, self)
@@ -272,7 +199,7 @@ function Feather:observe(key, value)
272199

273200
self.observers = self.observers or {}
274201

275-
local curr = self:__format(value)
202+
local curr = format(value)
276203

277204
for _, observer in ipairs(self.observers) do
278205
if observer.key == key then
@@ -287,13 +214,13 @@ end
287214
---@alias FeatherClear fun(self: Feather)
288215
---@type FeatherClear
289216
function Feather:clear()
290-
logs = {}
217+
self.featherLogger:clear()
291218
end
292219

293220
---@alias FeatherFinish fun(self: Feather)
294221
---@type FeatherFinish
295222
function Feather:finish()
296-
self:log({ type = "feather:finish" })
223+
self.featherLogger:log({ type = "feather:finish" })
297224

298225
self.pluginManager:finish(self)
299226
end
@@ -312,57 +239,46 @@ function Feather:update(dt)
312239

313240
local client = self.server:accept()
314241
if client then
315-
if #logs == 0 then
316-
self:log({ type = "feather:start" })
242+
if #self.featherLogger.logs == 0 then
243+
self.featherLogger:log({ type = "feather:start" })
317244
end
318245

319246
client:settimeout(1)
320247

321248
local rawRequest = client:receive()
322-
local request = self:__buildRequest(rawRequest)
249+
local request = serverUtils.buildRequest(rawRequest)
323250

324251
local addr = client:getsockname()
325-
326-
self.logger(request)
327-
if not self:__isInWhitelist(addr) then
252+
if not serverUtils.isInWhitelist(addr, self.whitelist) then
328253
self:trace("non-whitelisted connection attempt: ", addr)
329254
client:close()
330255
end
331-
332-
self.logger(request.method)
333256
if request and request.method == "GET" then
334257
local response = ""
335-
336-
self.logger(request.path)
337258
if request.path == "/config" then
338-
local body = json.encode(self:__getConfig())
339-
response = self:__buildResponse(body)
259+
response = serverUtils.createResponse(self:__getConfig())
340260
end
341261

342262
if request.path == "/logs" then
343-
local body = json.encode(logs)
344-
response = self:__buildResponse(body)
263+
response = serverUtils.createResponse(self.featherLogger.logs)
345264
self.lastDelivery = os.time()
346265
end
347266

348267
if request.path == "/performance" then
349-
local body = json.encode(performance:getResponseBody(dt))
350-
response = self:__buildResponse(body)
268+
response = serverUtils.createResponse(performance:getResponseBody(dt))
351269
end
352270

353271
if request.path == "/observers" then
354272
if self.defaultObservers then
355273
self:__defaultObservers()
356274
end
357-
local body = json.encode(self.observers)
358-
response = self:__buildResponse(body)
275+
response = serverUtils.createResponse(self.observers)
359276
end
360277

361278
if request.path == "/plugins" then
362279
local pluginResponse = self.pluginManager:handleRequest(request, self)
363280

364-
local body = json.encode(pluginResponse)
365-
response = self:__buildResponse(body)
281+
response = serverUtils.createResponse(pluginResponse)
366282
end
367283

368284
client:send(response)
@@ -380,64 +296,10 @@ function Feather:trace(...)
380296
return
381297
end
382298

383-
local str = "[Feather] " .. self:__format(...)
384-
385-
self.logger(str)
386-
self:__print("trace", str)
387-
end
388-
389-
---@alias LogType "output" | "trace" | "error" | "feather:finish" | "feather:start" | "output" | "error"
390-
---@class FeatherLine
391-
---@field type LogType
392-
---@field str? string
393-
---@field id? string
394-
---@field time? number
395-
---@field count? number
396-
---@field trace? string
397-
---@alias FeatherLog fun(self: Feather, line: FeatherLine)
398-
---@type FeatherLog
399-
function Feather:log(line)
400-
if not self.debug then
401-
return
402-
end
403-
404-
line.id = tostring(os.time()) .. "-" .. tostring(#logs + 1)
405-
line.time = os.time()
406-
line.count = 1
407-
line.trace = debug.traceback()
408-
409-
table.insert(logs, line)
410-
411-
--- Find a way to avoid deleting incoming logs
412-
if #logs > self.maxTempLogs then
413-
table.remove(logs, 1)
414-
end
415-
end
416-
417-
---@alias FeatherPrint fun(self: Feather, ...)
418-
---@type FeatherPrint
419-
function Feather:print(...)
420-
self:__countOnRepeat("output", ...)
421-
end
422-
423-
--- Manages the print function internally
424-
--- @param self Feather
425-
--- @param type LogType
426-
--- @param ... unknown
427-
function Feather:__countOnRepeat(type, ...)
428-
if not self.debug then
429-
return
430-
end
299+
local str = "[Feather] " .. format(...)
431300

432-
local str = self:__format(...)
433-
local last = logs[#logs]
434-
if last and str == last.str then
435-
-- Update last line if this line is a duplicate of it
436-
last.time = os.time()
437-
last.count = last.count + 1
438-
else
439-
self:log({ type = type, str = str })
440-
end
301+
self.featherLogger.logger(str)
302+
self.featherLogger:print("trace", str)
441303
end
442304

443305
---@type fun(config: FeatherConfig): Feather

src-lua/feather/plugin_manager.lua

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,19 @@ local PATH = string.sub(..., 1, string.len(...) - string.len("plugin_manager"))
22

33
local Class = require(PATH .. ".lib.class")
44

5+
--- @class FeatherPluginInstance
6+
--- @field instance FeatherPlugin
7+
--- @field identifier string
8+
9+
---@class FeatherPluginManager
10+
---@field plugins FeatherPluginInstance[]
511
local FeatherPluginManager = Class({})
612

7-
function FeatherPluginManager:init(feather, logs)
13+
---@param logger FeatherLogger
14+
---@param feather Feather
15+
function FeatherPluginManager:init(feather, logger)
816
self.plugins = {}
17+
self.logger = logger
918

1019
if not feather.plugins then
1120
return
@@ -17,23 +26,18 @@ function FeatherPluginManager:init(feather, logs)
1726
local ok, pluginInstance = pcall(plugin.plugin, {
1827
options = plugin.options,
1928
feather = feather,
29+
logger = logger,
2030
})
2131

2232
if ok then
23-
pluginInstance.logger = feather.logger
33+
pluginInstance.logger = logger
2434

2535
table.insert(self.plugins, {
2636
instance = pluginInstance,
2737
identifier = plugin.identifier,
2838
})
2939
else
30-
-- TODO: Make logger a shared plugin independent from feather
31-
logs[#logs + 1] = {
32-
type = "error",
33-
str = debug.traceback(pluginInstance),
34-
count = 1,
35-
time = os.time(),
36-
}
40+
self.logger:log({ type = "error", str = debug.traceback(pluginInstance) })
3741
end
3842
end
3943
end
@@ -74,4 +78,16 @@ function FeatherPluginManager.createPlugin(plugin, identifier, options)
7478
}
7579
end
7680

81+
function FeatherPluginManager:getConfig()
82+
local pluginsConfig = {}
83+
84+
for _, plugin in ipairs(self.plugins) do
85+
local config = plugin.instance:getConfig()
86+
87+
pluginsConfig[plugin.identifier] = config
88+
end
89+
90+
return pluginsConfig
91+
end
92+
7793
return FeatherPluginManager

0 commit comments

Comments
 (0)