dispatcher.lua 5.7 KB

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