123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- -- Copyright 2008 Steven Barth <steven@midlink.org>
- -- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org>
- -- Copyright 2017-2018 Matthias Schiffer <mschiffer@universe-factory.net>
- -- Licensed to the public under the Apache License 2.0.
- local fs = require "nixio.fs"
- local json = require "jsonc"
- local tpl = require "gluon.web.template"
- local util = require "gluon.web.util"
- local proto = require "gluon.web.http.protocol"
- module("gluon.web.dispatcher", package.seeall)
- function build_url(http, path)
- return (http:getenv("SCRIPT_NAME") or "") .. "/" .. table.concat(path, "/")
- end
- function redirect(http, ...)
- http:redirect(build_url(http, {...}))
- end
- function node_visible(node)
- return (
- node.title and
- node.target and
- (not node.hidden)
- )
- end
- function node_children(node)
- if not node then return {} end
- local ret = {}
- for k, v in pairs(node.nodes) do
- if node_visible(v) then
- table.insert(ret, k)
- end
- end
- table.sort(ret,
- function(a, b)
- return (node.nodes[a].order or 100)
- < (node.nodes[b].order or 100)
- end
- )
- return ret
- end
- function httpdispatch(http)
- local request = {}
- local pathinfo = proto.urldecode(http:getenv("PATH_INFO") or "", true)
- for node in pathinfo:gmatch("[^/]+") do
- table.insert(request, node)
- end
- ok, err = pcall(dispatch, http, request)
- if not ok then
- http:status(500, "Internal Server Error")
- http:prepare_content("text/plain")
- http:write(err)
- end
- end
- local function set_language(renderer, accept)
- local langs = {}
- local weights = {}
- local star = 0
- local function add(lang, q)
- if not weights[lang] then
- table.insert(langs, lang)
- weights[lang] = q
- end
- end
- for match in accept:gmatch("[^,]+") do
- local lang = match:match('^%s*([^%s;_-]+)')
- local q = tonumber(match:match(';q=(%S+)%s*$') or 1)
- if lang == '*' then
- star = q
- elseif lang and q > 0 then
- add(lang, q)
- end
- end
- add('en', star)
- table.sort(langs, function(a, b)
- return (weights[a] or 0) > (weights[b] or 0)
- end)
- renderer.set_language(langs)
- end
- function dispatch(http, request)
- local tree = {nodes={}}
- local nodes = {[''] = tree}
- local function _node(path, create)
- local name = table.concat(path, ".")
- local c = nodes[name]
- if not c and create then
- local last = table.remove(path)
- local parent = _node(path, true)
- c = {nodes={}}
- parent.nodes[last] = c
- nodes[name] = c
- end
- return c
- end
- -- Init template engine
- local function attr(key, val)
- if not val then
- return ''
- end
- if type(val) == "table" then
- val = json.stringify(val)
- end
- return string.format(' %s="%s"', key, util.pcdata(tostring(val)))
- end
- local renderer = tpl.renderer(setmetatable({
- http = http,
- request = request,
- node = function(path) return _node({path}) end,
- write = function(...) return http:write(...) end,
- pcdata = util.pcdata,
- urlencode = proto.urlencode,
- media = '/static/gluon',
- theme = 'gluon',
- resource = '/static/resources',
- attr = attr,
- url = function(path) return build_url(http, path) end,
- }, { __index = _G }))
- local function createtree()
- local base = util.libpath() .. "/controller/"
- local function load_ctl(path)
- local ctl = assert(loadfile(path))
- local _pkg
- local subdisp = setmetatable({
- package = function(name)
- _pkg = name
- end,
- node = function(...)
- return _node({...})
- end,
- entry = function(path, target, title, order)
- local c = _node(path, true)
- c.target = target
- c.title = title
- c.order = order
- c.pkg = _pkg
- return c
- end,
- alias = function(...)
- local req = {...}
- return function()
- http:redirect(build_url(http, req))
- end
- end,
- call = function(func, ...)
- local args = {...}
- return function()
- func(http, renderer, unpack(args))
- end
- end,
- template = function(view)
- local pkg = _pkg
- return function()
- renderer.render("layout", {content = view, pkg = pkg})
- end
- end,
- model = function(name)
- local pkg = _pkg
- return function()
- local hidenav = false
- local model = require "gluon.web.model"
- local maps = model.load(name, renderer, pkg)
- for _, map in ipairs(maps) do
- map:parse(http)
- end
- for _, map in ipairs(maps) do
- map:handle()
- hidenav = hidenav or map.hidenav
- end
- renderer.render("layout", {
- content = "model/wrapper",
- env = {
- maps = maps,
- },
- hidenav = hidenav,
- })
- end
- end,
- _ = function(text)
- return text
- end,
- }, { __index = _G })
- local env = setmetatable({}, { __index = subdisp })
- setfenv(ctl, env)
- ctl()
- end
- for path in (fs.glob(base .. "*.lua") or function() end) do
- load_ctl(path)
- end
- for path in (fs.glob(base .. "*/*.lua") or function() end) do
- load_ctl(path)
- end
- end
- set_language(renderer, http:getenv("HTTP_ACCEPT_LANGUAGE") or "")
- createtree()
- local node = _node(request)
- if not node or not node.target then
- http:status(404, "Not Found")
- renderer.render("layout", {
- content = "error404",
- env = {
- message =
- "No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
- "If this URL belongs to an extension, make sure it is properly installed.\n",
- },
- pkg = 'gluon-web',
- })
- return
- end
- http:parse_input(node.filehandler)
- local ok, err = pcall(node.target)
- if not ok then
- http:status(500, "Internal Server Error")
- renderer.render("layout", {
- content = "error500",
- env = {
- message =
- "Failed to execute dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
- "The called action terminated with an exception:\n" .. tostring(err or "(unknown)"),
- },
- pkg = 'gluon-web',
- })
- end
- end
|