diff --git a/modules/crypto/crypto.lua b/modules/crypto/crypto.lua index 0178322..2d7e934 100644 --- a/modules/crypto/crypto.lua +++ b/modules/crypto/crypto.lua @@ -1,4 +1,3 @@ -local json = require("json") local crypto = {} -- ====================================================================== diff --git a/modules/http/http.lua b/modules/http/http.lua index 93101c3..864d772 100644 --- a/modules/http/http.lua +++ b/modules/http/http.lua @@ -83,7 +83,7 @@ function Session:_ensure_loaded() if self._loaded then return end ensure_session_store() - + local config = _G._http_session_config local session_id = self._cookies[config.cookie_name] local session_data = nil @@ -157,22 +157,22 @@ function Session:save() local config = _G._http_session_config local session_json = json.encode(self._data) local success = kv.set(config.store_name, "session:" .. self.id, session_json) - + return success end function Session:destroy() self:_ensure_loaded() - + local config = _G._http_session_config kv.delete(config.store_name, "session:" .. self.id) - + -- Clear session cookie self._response:cookie(config.cookie_name, "", { expires = "Thu, 01 Jan 1970 00:00:00 GMT", path = config.cookie_options.path or "/" }) - + self.id = nil self._data = {} return self @@ -180,18 +180,118 @@ end function Session:regenerate() self:_ensure_loaded() - + local config = _G._http_session_config - + -- Delete old session if self.id then kv.delete(config.store_name, "session:" .. self.id) end - + -- Generate new ID and set cookie self.id = generate_session_id() self._response:cookie(config.cookie_name, self.id, config.cookie_options) - + + return self +end + +-- ====================================================================== +-- ROUTER CLASS +-- ====================================================================== + +local Router = {} +Router.__index = Router + +function Router.new() + return setmetatable({ + _middleware = {}, + _prefix = "" + }, Router) +end + +function Router:use(...) + local args = {...} + if #args == 1 and type(args[1]) == "function" then + table.insert(self._middleware, args[1]) + elseif #args == 2 and type(args[1]) == "string" and type(args[2]) == "function" then + -- Path-specific middleware for this router + table.insert(self._middleware, {path = args[1], handler = args[2]}) + else + error("Invalid arguments to use()") + end + return self +end + +function Router:group(path_prefix, callback) + local group_router = Router.new() + group_router._prefix = self._prefix .. path_prefix + + -- Inherit parent middleware + for _, mw in ipairs(self._middleware) do + table.insert(group_router._middleware, mw) + end + + if callback then + callback(group_router) + end + return group_router +end + +function Router:_add_route(method, path, handler) + if not string.starts_with(path, "/") then + path = "/" .. path + end + + local full_path = self._prefix .. path + local segments = split_path(full_path) + + -- Create middleware chain for this specific route + local route_middleware = {} + + -- Add global middleware first + for _, mw in ipairs(_G._http_middleware) do + if mw.path == nil or string.starts_with(full_path, mw.path) then + table.insert(route_middleware, mw.handler) + end + end + + -- Add router middleware + for _, mw in ipairs(self._middleware) do + if type(mw) == "function" then + table.insert(route_middleware, mw) + elseif type(mw) == "table" and mw.path then + -- Check if path-specific middleware applies + if string.starts_with(full_path, mw.path) then + table.insert(route_middleware, mw.handler) + end + end + end + + table.insert(_G._http_routes, { + method = method, + path = full_path, + segments = segments, + handler = handler, + middleware = route_middleware -- Store middleware per route + }) + + return self +end + +-- HTTP method helpers for Router +function Router:get(path, handler) return self:_add_route("GET", path, handler) end +function Router:post(path, handler) return self:_add_route("POST", path, handler) end +function Router:put(path, handler) return self:_add_route("PUT", path, handler) end +function Router:delete(path, handler) return self:_add_route("DELETE", path, handler) end +function Router:patch(path, handler) return self:_add_route("PATCH", path, handler) end +function Router:head(path, handler) return self:_add_route("HEAD", path, handler) end +function Router:options(path, handler) return self:_add_route("OPTIONS", path, handler) end + +function Router:all(path, handler) + local methods = {"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"} + for _, method in ipairs(methods) do + self:_add_route(method, path, handler) + end return self end @@ -491,32 +591,44 @@ local function match_route(method, path) return nil, {} end +-- ====================================================================== +-- OPTIMIZED REQUEST HANDLING +-- ====================================================================== + function _http_handle_request(req_table, res_table) local res = Response.new(res_table) local req = Request.new(req_table, res) + -- Fast route lookup + local route, params = match_route(req.method, req.path) + req.params = params + + if not route then + res:send(404) + return + end + + -- Fast path: no middleware + if not route.middleware or #route.middleware == 0 then + route.handler(req, res) + + -- Auto-save dirty sessions + if req.session._loaded and req.session._dirty then + req.session:save() + end + return + end + + -- Run route-specific middleware chain local function run_middleware(index) - if index > #_G._http_middleware then - local route, params = match_route(req.method, req.path) - req.params = params - - if not route then - res:send(404) - return - end - + if index > #route.middleware then route.handler(req, res) return end - local mw = _G._http_middleware[index] - if mw.path == nil or string.starts_with(req.path, mw.path) then - mw.handler(req, res, function() - run_middleware(index + 1) - end) - else + route.middleware[index](req, res, function() run_middleware(index + 1) - end + end) end run_middleware(1) @@ -569,23 +681,34 @@ end function Server:configure_sessions(options) options = options or {} - + local config = _G._http_session_config config.store_name = options.store_name or config.store_name config.filename = options.filename or config.filename config.cookie_name = options.cookie_name or config.cookie_name - + if options.cookie_options then for k, v in pairs(options.cookie_options) do config.cookie_options[k] = v end end - + ensure_session_store() return self end +function Server:group(path_prefix, callback) + local router = Router.new() + router._prefix = path_prefix + + if callback then + callback(router) + end + return router +end + function Server:use(...) + -- Global middleware - stored per route during registration local args = {...} if #args == 1 and type(args[1]) == "function" then table.insert(_G._http_middleware, {path = nil, handler = args[1]}) @@ -594,7 +717,6 @@ function Server:use(...) else error("Invalid arguments to use()") end - return self end @@ -605,11 +727,20 @@ function Server:_add_route(method, path, handler) local segments = split_path(path) + -- Apply global middleware to this route + local route_middleware = {} + for _, mw in ipairs(_G._http_middleware) do + if mw.path == nil or string.starts_with(path, mw.path) then + table.insert(route_middleware, mw.handler) + end + end + table.insert(_G._http_routes, { method = method, path = path, segments = segments, - handler = handler + handler = handler, + middleware = route_middleware }) return self @@ -671,6 +802,10 @@ end -- MIDDLEWARE HELPERS -- ====================================================================== +function http.router() + return Router.new() +end + function http.cors(options) options = options or {} local origin = options.origin or "*" @@ -770,12 +905,12 @@ end function http.cleanup_expired_sessions(max_age) max_age = max_age or 86400 ensure_session_store() - + local config = _G._http_session_config local keys = kv.keys(config.store_name) local current_time = os.time() local deleted = 0 - + for _, key in ipairs(keys) do if string.starts_with(key, "session:") then local json_str = kv.get(config.store_name, key) @@ -790,7 +925,7 @@ function http.cleanup_expired_sessions(max_age) end end end - + return deleted end @@ -799,14 +934,14 @@ function http.get_session_count() local config = _G._http_session_config local keys = kv.keys(config.store_name) local count = 0 - + for _, key in ipairs(keys) do if string.starts_with(key, "session:") then count = count + 1 end end - + return count end -return http \ No newline at end of file +return http