dispatcher.lua 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. -- Copyright 2008 Steven Barth <steven@midlink.org>
  2. -- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org>
  3. -- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
  4. -- Licensed to the public under the Apache License 2.0.
  5. local fs = require "nixio.fs"
  6. local tpl = require "gluon.web.template"
  7. local util = require "gluon.web.util"
  8. local proto = require "gluon.web.http.protocol"
  9. module("gluon.web.dispatcher", package.seeall)
  10. function build_url(http, path)
  11. return (http:getenv("SCRIPT_NAME") or "") .. "/" .. table.concat(path, "/")
  12. end
  13. function redirect(http, ...)
  14. http:redirect(build_url(http, {...}))
  15. end
  16. function node_visible(node)
  17. return (
  18. node.title and
  19. node.target and
  20. (not node.hidden)
  21. )
  22. end
  23. function node_children(node)
  24. if not node then return {} end
  25. local ret = {}
  26. for k, v in pairs(node.nodes) do
  27. if node_visible(v) then
  28. table.insert(ret, k)
  29. end
  30. end
  31. table.sort(ret,
  32. function(a, b)
  33. return (node.nodes[a].order or 100)
  34. < (node.nodes[b].order or 100)
  35. end
  36. )
  37. return ret
  38. end
  39. function httpdispatch(http)
  40. local request = {}
  41. local pathinfo = proto.urldecode(http:getenv("PATH_INFO") or "", true)
  42. for node in pathinfo:gmatch("[^/]+") do
  43. table.insert(request, node)
  44. end
  45. ok, err = pcall(dispatch, http, request)
  46. if not ok then
  47. http:status(500, "Internal Server Error")
  48. http:prepare_content("text/plain")
  49. http:write(err)
  50. end
  51. end
  52. local function set_language(renderer, accept)
  53. local langs = {}
  54. local weights = {}
  55. local star = 0
  56. local function add(lang, q)
  57. if not weights[lang] then
  58. table.insert(langs, lang)
  59. weights[lang] = q
  60. end
  61. end
  62. for match in accept:gmatch("[^,]+") do
  63. local lang = match:match('^%s*([^%s;-_]+)')
  64. local q = tonumber(match:match(';q=(%S+)%s*$') or 1)
  65. if lang == '*' then
  66. star = q
  67. elseif lang and q > 0 then
  68. add(lang, q)
  69. end
  70. end
  71. add('en', star)
  72. table.sort(langs, function(a, b)
  73. return (weights[a] or 0) > (weights[b] or 0)
  74. end)
  75. for _, lang in ipairs(langs) do
  76. if renderer.setlanguage(lang) then
  77. return
  78. end
  79. end
  80. end
  81. function dispatch(http, request)
  82. local tree = {nodes={}}
  83. local nodes = {[''] = tree}
  84. local function _node(path, create)
  85. local name = table.concat(path, ".")
  86. local c = nodes[name]
  87. if not c and create then
  88. local last = table.remove(path)
  89. local parent = _node(path, true)
  90. c = {nodes={}}
  91. parent.nodes[last] = c
  92. nodes[name] = c
  93. end
  94. return c
  95. end
  96. -- Init template engine
  97. local function attr(key, val)
  98. if not val then
  99. return ''
  100. end
  101. if type(val) == "table" then
  102. val = util.serialize_json(val)
  103. end
  104. return string.format(' %s="%s"', key, util.pcdata(tostring(val)))
  105. end
  106. local renderer = tpl.renderer(setmetatable({
  107. http = http,
  108. request = request,
  109. node = function(path) return _node({path}) end,
  110. write = function(...) return http:write(...) end,
  111. pcdata = util.pcdata,
  112. urlencode = proto.urlencode,
  113. media = '/static/gluon',
  114. theme = 'gluon',
  115. resource = '/static/resources',
  116. attr = attr,
  117. url = function(path) return build_url(http, path) end,
  118. }, { __index = _G }))
  119. local subdisp = setmetatable({
  120. node = function(...)
  121. return _node({...})
  122. end,
  123. entry = function(path, target, title, order)
  124. local c = _node(path, true)
  125. c.target = target
  126. c.title = title
  127. c.order = order
  128. return c
  129. end,
  130. alias = function(...)
  131. local req = {...}
  132. return function()
  133. http:redirect(build_url(http, req))
  134. end
  135. end,
  136. call = function(func, ...)
  137. local args = {...}
  138. return function()
  139. func(http, renderer, unpack(args))
  140. end
  141. end,
  142. template = function(view)
  143. return function()
  144. renderer.render("layout", {content = view})
  145. end
  146. end,
  147. model = function(name)
  148. return function()
  149. local hidenav = false
  150. local model = require "gluon.web.model"
  151. local maps = model.load(name, renderer)
  152. for _, map in ipairs(maps) do
  153. map:parse(http)
  154. end
  155. for _, map in ipairs(maps) do
  156. map:handle()
  157. hidenav = hidenav or map.hidenav
  158. end
  159. renderer.render("layout", {
  160. content = "model/wrapper",
  161. maps = maps,
  162. hidenav = hidenav,
  163. })
  164. end
  165. end,
  166. _ = function(text)
  167. return text
  168. end,
  169. }, { __index = _G })
  170. local function createtree()
  171. local base = util.libpath() .. "/controller/"
  172. local function load_ctl(path)
  173. local ctl = assert(loadfile(path))
  174. local env = setmetatable({}, { __index = subdisp })
  175. setfenv(ctl, env)
  176. ctl()
  177. end
  178. for path in (fs.glob(base .. "*.lua") or function() end) do
  179. load_ctl(path)
  180. end
  181. for path in (fs.glob(base .. "*/*.lua") or function() end) do
  182. load_ctl(path)
  183. end
  184. end
  185. set_language(renderer, http:getenv("HTTP_ACCEPT_LANGUAGE") or "")
  186. createtree()
  187. local node = _node(request)
  188. if not node or not node.target then
  189. http:status(404, "Not Found")
  190. renderer.render("layout", { content = "error404", message =
  191. "No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
  192. "If this URL belongs to an extension, make sure it is properly installed.\n"
  193. })
  194. return
  195. end
  196. http:parse_input(node.filehandler)
  197. local ok, err = pcall(node.target)
  198. if not ok then
  199. http:status(500, "Internal Server Error")
  200. renderer.render("layout", { content = "error500", message =
  201. "Failed to execute dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
  202. "The called action terminated with an exception:\n" .. tostring(err or "(unknown)")
  203. })
  204. end
  205. end