/src/orbit.lua
http://github.com/keplerproject/orbit · Lua · 574 lines · 564 code · 10 blank · 0 comment · 18 complexity · a35ed7e0530575df01700d576ea5578a MD5 · raw file
- local wsapi = require "wsapi"
- local wsreq = require "wsapi.request"
- local wsres = require "wsapi.response"
- local wsutil = require "wsapi.util"
- local orm
- local orpages
- local _M = _M or {}
- _M._NAME = "orbit"
- _M._VERSION = "2.2.4"
- _M._COPYRIGHT = "Copyright (C) 2007-2015 Kepler Project"
- _M._DESCRIPTION = "MVC Web Development for the Kepler platform"
- local REPARSE = {}
- _M.mime_types = {
- ez = "application/andrew-inset",
- atom = "application/atom+xml",
- hqx = "application/mac-binhex40",
- cpt = "application/mac-compactpro",
- mathml = "application/mathml+xml",
- doc = "application/msword",
- bin = "application/octet-stream",
- dms = "application/octet-stream",
- lha = "application/octet-stream",
- lzh = "application/octet-stream",
- exe = "application/octet-stream",
- class = "application/octet-stream",
- so = "application/octet-stream",
- dll = "application/octet-stream",
- dmg = "application/octet-stream",
- oda = "application/oda",
- ogg = "application/ogg",
- pdf = "application/pdf",
- ai = "application/postscript",
- eps = "application/postscript",
- ps = "application/postscript",
- rdf = "application/rdf+xml",
- smi = "application/smil",
- smil = "application/smil",
- gram = "application/srgs",
- grxml = "application/srgs+xml",
- mif = "application/vnd.mif",
- xul = "application/vnd.mozilla.xul+xml",
- xls = "application/vnd.ms-excel",
- ppt = "application/vnd.ms-powerpoint",
- rm = "application/vnd.rn-realmedia",
- wbxml = "application/vnd.wap.wbxml",
- wmlc = "application/vnd.wap.wmlc",
- wmlsc = "application/vnd.wap.wmlscriptc",
- vxml = "application/voicexml+xml",
- bcpio = "application/x-bcpio",
- vcd = "application/x-cdlink",
- pgn = "application/x-chess-pgn",
- cpio = "application/x-cpio",
- csh = "application/x-csh",
- dcr = "application/x-director",
- dir = "application/x-director",
- dxr = "application/x-director",
- dvi = "application/x-dvi",
- spl = "application/x-futuresplash",
- gtar = "application/x-gtar",
- hdf = "application/x-hdf",
- xhtml = "application/xhtml+xml",
- xht = "application/xhtml+xml",
- js = "application/x-javascript",
- skp = "application/x-koan",
- skd = "application/x-koan",
- skt = "application/x-koan",
- skm = "application/x-koan",
- latex = "application/x-latex",
- xml = "application/xml",
- xsl = "application/xml",
- dtd = "application/xml-dtd",
- nc = "application/x-netcdf",
- cdf = "application/x-netcdf",
- sh = "application/x-sh",
- shar = "application/x-shar",
- swf = "application/x-shockwave-flash",
- xslt = "application/xslt+xml",
- sit = "application/x-stuffit",
- sv4cpio = "application/x-sv4cpio",
- sv4crc = "application/x-sv4crc",
- tar = "application/x-tar",
- tcl = "application/x-tcl",
- tex = "application/x-tex",
- texinfo = "application/x-texinfo",
- texi = "application/x-texinfo",
- t = "application/x-troff",
- tr = "application/x-troff",
- roff = "application/x-troff",
- man = "application/x-troff-man",
- me = "application/x-troff-me",
- ms = "application/x-troff-ms",
- ustar = "application/x-ustar",
- src = "application/x-wais-source",
- zip = "application/zip",
- au = "audio/basic",
- snd = "audio/basic",
- mid = "audio/midi",
- midi = "audio/midi",
- kar = "audio/midi",
- mpga = "audio/mpeg",
- mp2 = "audio/mpeg",
- mp3 = "audio/mpeg",
- aif = "audio/x-aiff",
- aiff = "audio/x-aiff",
- aifc = "audio/x-aiff",
- m3u = "audio/x-mpegurl",
- ram = "audio/x-pn-realaudio",
- ra = "audio/x-pn-realaudio",
- wav = "audio/x-wav",
- pdb = "chemical/x-pdb",
- xyz = "chemical/x-xyz",
- bmp = "image/bmp",
- cgm = "image/cgm",
- gif = "image/gif",
- ief = "image/ief",
- jpeg = "image/jpeg",
- jpg = "image/jpeg",
- jpe = "image/jpeg",
- png = "image/png",
- svg = "image/svg+xml",
- svgz = "image/svg+xml",
- tiff = "image/tiff",
- tif = "image/tiff",
- djvu = "image/vnd.djvu",
- djv = "image/vnd.djvu",
- wbmp = "image/vnd.wap.wbmp",
- ras = "image/x-cmu-raster",
- ico = "image/x-icon",
- pnm = "image/x-portable-anymap",
- pbm = "image/x-portable-bitmap",
- pgm = "image/x-portable-graymap",
- ppm = "image/x-portable-pixmap",
- rgb = "image/x-rgb",
- xbm = "image/x-xbitmap",
- xpm = "image/x-xpixmap",
- xwd = "image/x-xwindowdump",
- igs = "model/iges",
- iges = "model/iges",
- msh = "model/mesh",
- mesh = "model/mesh",
- silo = "model/mesh",
- wrl = "model/vrml",
- vrml = "model/vrml",
- ics = "text/calendar",
- ifb = "text/calendar",
- css = "text/css",
- html = "text/html",
- htm = "text/html",
- asc = "text/plain",
- txt = "text/plain",
- rtx = "text/richtext",
- rtf = "text/rtf",
- sgml = "text/sgml",
- sgm = "text/sgml",
- tsv = "text/tab-separated-values",
- wml = "text/vnd.wap.wml",
- wmls = "text/vnd.wap.wmlscript",
- etx = "text/x-setext",
- mpeg = "video/mpeg",
- mpg = "video/mpeg",
- mpe = "video/mpeg",
- qt = "video/quicktime",
- mov = "video/quicktime",
- mxu = "video/vnd.mpegurl",
- avi = "video/x-msvideo",
- movie = "video/x-sgi-movie",
- ice = "x-conference/x-cooltalk",
- rss = "application/rss+xml",
- atom = "application/atom+xml",
- json = "application/json"
- }
- _M.app_module_methods = {}
- local app_module_methods = _M.app_module_methods
- _M.web_methods = {}
- local web_methods = _M.web_methods
- local function flatten(t)
- local res = {}
- for _, item in ipairs(t) do
- if type(item) == "table" then
- res[#res + 1] = flatten(item)
- else
- res[#res + 1] = item
- end
- end
- return table.concat(res)
- end
- local function make_tag(name, data, class)
- if class then class = ' class="' .. class .. '"' else class = "" end
- if not data then
- return "<" .. name .. class .. "/>"
- elseif type(data) == "string" then
- return "<" .. name .. class .. ">" .. data ..
- "</" .. name .. ">"
- else
- local attrs = {}
- for k, v in pairs(data) do
- if type(k) == "string" then
- table.insert(attrs, k .. '="' .. tostring(v) .. '"')
- end
- end
- local open_tag = "<" .. name .. class .. " " ..
- table.concat(attrs, " ") .. ">"
- local close_tag = "</" .. name .. ">"
- return open_tag .. flatten(data) .. close_tag
- end
- end
- function _M.new(app_module)
- if type(app_module) == "string" then
- app_module = { _NAME = app_module }
- else
- app_module = app_module or {}
- end
- for k, v in pairs(app_module_methods) do
- app_module[k] = v
- end
- app_module.run = function (wsapi_env)
- return _M.run(app_module, wsapi_env)
- end
- app_module.real_path = wsapi.app_path or "."
- app_module.mapper = { default = true }
- app_module.not_found = function (web)
- web.status = "404 Not Found"
- return [[<html>
- <head><title>Not Found</title></head>
- <body><p>Not found!</p></body></html>]]
- end
- app_module.server_error = function (web, msg)
- web.status = "500 Server Error"
- return [[<html>
- <head><title>Server Error</title></head>
- <body><pre>]] .. msg .. [[</pre></body></html>]]
- end
- app_module.reparse = REPARSE
- app_module.dispatch_table = { get = {}, post = {}, put = {}, delete = {}, options = {} }
- return app_module
- end
- local function serve_file(app_module)
- return function (web)
- local filename = web.real_path .. web.path_info
- return app_module:serve_static(web, filename)
- end
- end
- function app_module_methods.dispatch_get(app_module, func, ...)
- for _, pat in ipairs{ ... } do
- table.insert(app_module.dispatch_table.get, { pattern = pat,
- handler = func })
- end
- end
- function app_module_methods.dispatch_post(app_module, func, ...)
- for _, pat in ipairs{ ... } do
- table.insert(app_module.dispatch_table.post, { pattern = pat,
- handler = func })
- end
- end
- function app_module_methods.dispatch_put(app_module, func, ...)
- for _, pat in ipairs{ ... } do
- table.insert(app_module.dispatch_table.put, { pattern = pat,
- handler = func })
- end
- end
- function app_module_methods.dispatch_delete(app_module, func, ...)
- for _, pat in ipairs{ ... } do
- table.insert(app_module.dispatch_table.delete, { pattern = pat,
- handler = func })
- end
- end
- function app_module_methods.dispatch_options(app_module, func, ...)
- for _, pat in ipairs{ ... } do
- table.insert(app_module.dispatch_table.options, { pattern = pat,
- handler = func })
- end
- end
- function app_module_methods.dispatch_wsapi(app_module, func, ...)
- for _, pat in ipairs{ ... } do
- for _, tab in pairs(app_module.dispatch_table) do
- table.insert(tab, { pattern = pat, handler = func, wsapi = true })
- end
- end
- end
- function app_module_methods.dispatch_static(app_module, ...)
- app_module:dispatch_get(serve_file(app_module), ...)
- end
- function app_module_methods.serve_static(app_module, web, filename)
- local ext = string.match(filename, "%.([^%.]+)$")
- if app_module.use_xsendfile then
- web.headers["Content-Type"] = _M.mime_types[ext] or
- "application/octet-stream"
- web.headers["X-Sendfile"] = filename
- return "xsendfile"
- else
- local file = io.open(filename, "rb")
- if not file then
- return app_module.not_found(web)
- else
- web.headers["Content-Type"] = _M.mime_types[ext] or
- "application/octet-stream"
- local contents = file:read("*a")
- file:close()
- return contents
- end
- end
- end
- local function newtag(name)
- local tag = {}
- setmetatable(tag, {
- __call = function (_, data)
- return make_tag(name, data)
- end,
- __index = function(_, class)
- return function (data)
- return make_tag(name, data, class)
- end
- end
- })
- return tag
- end
- local function htmlify_func(func)
- local tags = {}
- local env = { H = function (name)
- local tag = tags[name]
- if not tag then
- tag = newtag(name)
- tags[name] = tag
- end
- return tag
- end
- }
- local old_env = getfenv(func)
- setmetatable(env, { __index = function (env, name)
- if old_env[name] then
- return old_env[name]
- else
- local tag = newtag(name)
- rawset(env, name, tag)
- return tag
- end
- end })
- setfenv(func, env)
- end
- function _M.htmlify(app_module, ...)
- if type(app_module) == "function" then
- htmlify_func(app_module)
- for _, func in ipairs{...} do
- htmlify_func(func)
- end
- else
- local patterns = { ... }
- for _, patt in ipairs(patterns) do
- if type(patt) == "function" then
- htmlify_func(patt)
- else
- for name, func in pairs(app_module) do
- if string.match(name, "^" .. patt .. "$") and
- type(func) == "function" then
- htmlify_func(func)
- end
- end
- end
- end
- end
- end
- app_module_methods.htmlify = _M.htmlify
- function app_module_methods.model(app_module, ...)
- if app_module.mapper.default then
- local table_prefix = (app_module._NAME and app_module._NAME .. "_") or ""
- if not orm then
- orm = require "orbit.model"
- end
- app_module.mapper = orm.new(app_module.mapper.table_prefix or table_prefix,
- app_module.mapper.conn, app_module.mapper.driver, app_module.mapper.logging)
- end
- return app_module.mapper:new(...)
- end
- function web_methods:redirect(url)
- self.status = "302 Found"
- self.headers["Location"] = url
- return "redirect"
- end
- function web_methods:link(url, params)
- local link = {}
- local prefix = self.prefix or ""
- local suffix = self.suffix or ""
- for k, v in pairs(params or {}) do
- link[#link + 1] = k .. "=" .. wsutil.url_encode(v)
- end
- local qs = table.concat(link, "&")
- if qs and qs ~= "" then
- return prefix .. url .. suffix .. "?" .. qs
- else
- return prefix .. url .. suffix
- end
- end
- function web_methods:static_link(url)
- local prefix = self.prefix or self.script_name
- local is_script = prefix:match("(%.%w+)$")
- if not is_script then return self:link(url) end
- local vpath = prefix:match("(.*)/") or ""
- return vpath .. url
- end
- function web_methods:empty(s)
- return not s or string.match(s, "^%s*$")
- end
- function web_methods:content_type(s)
- self.headers["Content-Type"] = s
- end
- function web_methods:page(name, env)
- if not orpages then
- orpages = require "orbit.pages"
- end
- local filename
- if name:sub(1, 1) == "/" then
- filename = self.doc_root .. name
- else
- filename = self.real_path .. "/" .. name
- end
- local template = orpages.load(filename)
- if template then
- return orpages.fill(self, template, env)
- end
- end
- function web_methods:page_inline(contents, env)
- if not orpages then
- orpages = require "orbit.pages"
- end
- local template = orpages.load(nil, contents)
- if template then
- return orpages.fill(self, template, env)
- end
- end
- function web_methods:empty_param(param)
- return self:empty(self.input[param])
- end
- for name, func in pairs(wsutil) do
- web_methods[name] = function (self, ...)
- return func(...)
- end
- end
- local function dispatcher(app_module, method, path, index)
- index = index or 0
- if #app_module.dispatch_table[method] == 0 then
- return app_module["handle_" .. method], {}
- else
- for index = index+1, #app_module.dispatch_table[method] do
- local item = app_module.dispatch_table[method][index]
- local captures
- if type(item.pattern) == "string" then
- captures = { string.match(path, "^" .. item.pattern .. "$") }
- else
- captures = { item.pattern:match(path) }
- end
- if #captures > 0 then
- for i = 1, #captures do
- if type(captures[i]) == "string" then
- captures[i] = wsutil.url_decode(captures[i])
- end
- end
- return item.handler, captures, item.wsapi, index
- end
- end
- end
- end
- local function make_web_object(app_module, wsapi_env)
- local web = { status = "200 Ok", response = "",
- headers = { ["Content-Type"]= "text/html" },
- cookies = {} }
- setmetatable(web, { __index = web_methods })
- web.vars = wsapi_env
- web.prefix = app_module.prefix or wsapi_env.SCRIPT_NAME
- web.suffix = app_module.suffix
- if wsapi_env.APP_PATH == "" then
- web.real_path = app_module.real_path or "."
- else
- web.real_path = wsapi_env.APP_PATH
- end
- web.doc_root = wsapi_env.DOCUMENT_ROOT
- local req = wsreq.new(wsapi_env)
- local res = wsres.new(web.status, web.headers)
- web.set_cookie = function (_, name, value)
- res:set_cookie(name, value)
- end
- web.delete_cookie = function (_, name, path)
- res:delete_cookie(name, path)
- end
- web.path_info = req.path_info
- web.path_translated = wsapi_env.PATH_TRANSLATED
- if web.path_translated == "" then web.path_translated = wsapi_env.SCRIPT_FILENAME end
- web.script_name = wsapi_env.SCRIPT_NAME
- web.method = string.lower(req.method)
- web.input, web.cookies = req.params, req.cookies
- web.GET, web.POST = req.GET, req.POST
- return web, res
- end
- function _M.run(app_module, wsapi_env)
- local handler, captures, wsapi_handler, index = dispatcher(app_module,
- string.lower(wsapi_env.REQUEST_METHOD),
- wsapi_env.PATH_INFO)
- handler = handler or app_module.not_found
- captures = captures or {}
- if wsapi_handler then
- local ok, status, headers, res = xpcall(function ()
- return handler(wsapi_env, unpack(captures))
- end, debug.traceback)
- if ok then
- return status, headers, res
- else
- handler, captures = app_module.server_error, { status }
- end
- end
- local web, res = make_web_object(app_module, wsapi_env)
- repeat
- local reparse = false
- local ok, response = xpcall(function ()
- return handler(web, unpack(captures))
- end, function(msg) return debug.traceback(msg) end)
- if not ok then
- res.status = "500 Internal Server Error"
- res:write(app_module.server_error(web, response))
- else
- if response == REPARSE then
- reparse = true
- handler, captures, wsapi_handler, index = dispatcher(app_module,
- string.lower(wsapi_env.REQUEST_METHOD),
- wsapi_env.PATH_INFO, index)
- handler, captures = handler or app_module.not_found, captures or {}
- if wsapi_handler then
- error("cannot reparse to WSAPI handler")
- end
- else
- res.status = web.status
- res:write(response)
- end
- end
- until not reparse
- return res:finish()
- end
- return _M