220 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			220 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
-- 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 glob = require 'posix.glob'
 | 
						|
local json = require "jsonc"
 | 
						|
local tpl = require "gluon.web.template"
 | 
						|
local util = require "gluon.web.util"
 | 
						|
local proto = require "gluon.web.http.protocol"
 | 
						|
 | 
						|
 | 
						|
local function build_url(http, path)
 | 
						|
	return (http:getenv("SCRIPT_NAME") or "") .. "/" .. table.concat(path, "/")
 | 
						|
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
 | 
						|
 | 
						|
local function dispatch(config, 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(config, setmetatable({
 | 
						|
		http        = http,
 | 
						|
		request     = request,
 | 
						|
		node        = function(path) return _node({path}) end,
 | 
						|
		write       = function(...) return http:write(...) end,
 | 
						|
		pcdata      = util.pcdata,
 | 
						|
		urlencode   = proto.urlencode,
 | 
						|
		attr        = attr,
 | 
						|
		json        = json.stringify,
 | 
						|
		url         = function(path) return build_url(http, path) end,
 | 
						|
	}, { __index = _G }))
 | 
						|
 | 
						|
 | 
						|
	local function createtree()
 | 
						|
		local base = config.base_path .. "/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(entry_path, target, title, order)
 | 
						|
					local c = _node(entry_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, scope)
 | 
						|
					local pkg = _pkg
 | 
						|
					return function()
 | 
						|
						renderer.render_layout(view, scope, pkg)
 | 
						|
					end
 | 
						|
				end,
 | 
						|
 | 
						|
				model = function(name)
 | 
						|
					local pkg = _pkg
 | 
						|
					return function()
 | 
						|
						require('gluon.web.model')(config, http, renderer, name, pkg)
 | 
						|
					end
 | 
						|
				end,
 | 
						|
 | 
						|
				_ = function(text)
 | 
						|
					return text
 | 
						|
				end,
 | 
						|
			}, { __index = _G })
 | 
						|
 | 
						|
			local env = setmetatable({}, { __index = subdisp })
 | 
						|
			setfenv(ctl, env)
 | 
						|
 | 
						|
			ctl()
 | 
						|
		end
 | 
						|
 | 
						|
		for _, path in ipairs(glob.glob(base .. "*.lua", 0) or {}) do
 | 
						|
			load_ctl(path)
 | 
						|
		end
 | 
						|
		for _, path in ipairs(glob.glob(base .. "*/*.lua", 0) or {}) 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("error/404", {
 | 
						|
			message =
 | 
						|
				"No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
 | 
						|
				"If this URL belongs to an extension, make sure it is properly installed.\n",
 | 
						|
		}, 'gluon-web')
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	local ok, err = pcall(http.parse_input, http, node.filehandler)
 | 
						|
	if not ok then
 | 
						|
		http:status(400, "Bad request")
 | 
						|
		http:prepare_content("text/plain")
 | 
						|
		http:write(err .. "\r\n")
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	ok, err = pcall(node.target)
 | 
						|
	if not ok then
 | 
						|
		http:status(500, "Internal Server Error")
 | 
						|
		renderer.render_layout("error/500", {
 | 
						|
			message =
 | 
						|
				"Failed to execute dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
 | 
						|
				"The called action terminated with an exception:\n" .. tostring(err or "(unknown)"),
 | 
						|
		}, 'gluon-web')
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
return function(config, http)
 | 
						|
	local request = {}
 | 
						|
	local pathinfo = proto.urldecode(http:getenv("PATH_INFO") or "", true)
 | 
						|
	for node in pathinfo:gmatch("[^/]+") do
 | 
						|
		table.insert(request, node)
 | 
						|
	end
 | 
						|
 | 
						|
	local ok, err = pcall(dispatch, config, http, request)
 | 
						|
	if not ok then
 | 
						|
		http:status(500, "Internal Server Error")
 | 
						|
		http:prepare_content("text/plain")
 | 
						|
		http:write(err .. "\r\n")
 | 
						|
	end
 | 
						|
end
 |