diff options
Diffstat (limited to 'plugins/weblit/app.lua')
-rw-r--r-- | plugins/weblit/app.lua | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/plugins/weblit/app.lua b/plugins/weblit/app.lua new file mode 100644 index 00000000000..d3839c1df8e --- /dev/null +++ b/plugins/weblit/app.lua @@ -0,0 +1,261 @@ + + +local createServer = require('coro-net').createServer +local wrapper = require('coro-wrapper') +local readWrap, writeWrap = wrapper.reader, wrapper.writer +local httpCodec = require('http-codec') +--local tlsWrap = require('coro-tls').wrap +local parseQuery = require('querystring').parse + +-- Ignore SIGPIPE if it exists on platform +local uv = require('luv') +if uv.constants.SIGPIPE then + uv.new_signal():start("sigpipe") +end + +local server = {} +local handlers = {} +local bindings = {} + +-- Provide a nice case insensitive interface to headers. +local headerMeta = { + __index = function (list, name) + if type(name) ~= "string" then + return rawget(list, name) + end + name = name:lower() + for i = 1, #list do + local key, value = unpack(list[i]) + if key:lower() == name then return value end + end + end, + __newindex = function (list, name, value) + -- non-string keys go through as-is. + if type(name) ~= "string" then + return rawset(list, name, value) + end + -- First remove any existing pairs with matching key + local lowerName = name:lower() + for i = #list, 1, -1 do + if list[i][1]:lower() == lowerName then + table.remove(list, i) + end + end + -- If value is nil, we're done + if value == nil then return end + -- Otherwise, set the key(s) + if (type(value) == "table") then + -- We accept a table of strings + for i = 1, #value do + rawset(list, #list + 1, {name, tostring(value[i])}) + end + else + -- Or a single value interperted as string + rawset(list, #list + 1, {name, tostring(value)}) + end + end, +} + +local function handleRequest(head, input, socket) + local req = { + socket = socket, + method = head.method, + path = head.path, + headers = setmetatable({}, headerMeta), + version = head.version, + keepAlive = head.keepAlive, + body = input + } + for i = 1, #head do + req.headers[i] = head[i] + end + + local res = { + code = 404, + headers = setmetatable({}, headerMeta), + body = "Not Found\n", + } + + local function run(i) + local success, err = pcall(function () + i = i or 1 + local go = i < #handlers + and function () + return run(i + 1) + end + or function () end + return handlers[i](req, res, go) + end) + if not success then + res.code = 500 + res.headers = setmetatable({}, headerMeta) + res.body = err + print(err) + end + end + run(1) + + local out = { + code = res.code, + keepAlive = res.keepAlive, + } + for i = 1, #res.headers do + out[i] = res.headers[i] + end + return out, res.body, res.upgrade +end + +local function handleConnection(rawRead, rawWrite, socket) + + -- Speak in HTTP events + local read, updateDecoder = readWrap(rawRead, httpCodec.decoder()) + local write, updateEncoder = writeWrap(rawWrite, httpCodec.encoder()) + + for head in read do + local parts = {} + for chunk in read do + if #chunk > 0 then + parts[#parts + 1] = chunk + else + break + end + end + local res, body, upgrade = handleRequest(head, #parts > 0 and table.concat(parts) or nil, socket) + write(res) + if upgrade then + return upgrade(read, write, updateDecoder, updateEncoder, socket) + end + write(body) + if not (res.keepAlive and head.keepAlive) then + break + end + end + write() + +end + +function server.bind(options) + if not options.host then + options.host = "127.0.0.1" + end + if not options.port then + options.port = require('uv').getuid() == 0 and + (options.tls and 443 or 80) or + (options.tls and 8443 or 8080) + end + bindings[#bindings + 1] = options + return server +end + +function server.use(handler) + handlers[#handlers + 1] = handler + return server +end + + +function server.start() + if #bindings == 0 then + server.bind({}) + end + for i = 1, #bindings do + local options = bindings[i] + createServer(options, function (rawRead, rawWrite, socket) + --local tls = options.tls + --if tls then + --rawRead, rawWrite = tlsWrap(rawRead, rawWrite, { + -- server = true, + --key = assert(tls.key, "tls key required"), + --cert = assert(tls.cert, "tls cert required"), + --}) + --end + return handleConnection(rawRead, rawWrite, socket) + end) + print("HTTP server listening at http" .. (options.tls and "s" or "") .. "://" .. options.host .. (options.port == (options.tls and 443 or 80) and "" or ":" .. options.port) .. "/") + end + return server +end + +local quotepattern = '(['..("%^$().[]*+-?"):gsub("(.)", "%%%1")..'])' +local function escape(str) + return str:gsub(quotepattern, "%%%1") +end + +local function compileGlob(glob) + local parts = {"^"} + for a, b in glob:gmatch("([^*]*)(%**)") do + if #a > 0 then + parts[#parts + 1] = escape(a) + end + if #b > 0 then + parts[#parts + 1] = "(.*)" + end + end + parts[#parts + 1] = "$" + local pattern = table.concat(parts) + return function (string) + return string and string:match(pattern) + end +end + +local function compileRoute(route) + local parts = {"^"} + local names = {} + for a, b, c, d in route:gmatch("([^:]*):([_%a][_%w]*)(:?)([^:]*)") do + if #a > 0 then + parts[#parts + 1] = escape(a) + end + if #c > 0 then + parts[#parts + 1] = "(.*)" + else + parts[#parts + 1] = "([^/]*)" + end + names[#names + 1] = b + if #d > 0 then + parts[#parts + 1] = escape(d) + end + end + if #parts == 1 then + return function (string) + if string == route then return {} end + end + end + parts[#parts + 1] = "$" + local pattern = table.concat(parts) + return function (string) + local matches = {string:match(pattern)} + if #matches > 0 then + local results = {} + for i = 1, #matches do + results[i] = matches[i] + results[names[i]] = matches[i] + end + return results + end + end +end + +function server.route(options, handler) + local method = options.method + local path = options.path and compileRoute(options.path) + local host = options.host and compileGlob(options.host) + local filter = options.filter + server.use(function (req, res, go) + if method and req.method ~= method then return go() end + if host and not host(req.headers.host) then return go() end + if filter and not filter(req) then return go() end + local params + if path then + local pathname, query = req.path:match("^([^?]*)%??(.*)") + params = path(pathname) + if not params then return go() end + if #query > 0 then + req.query = parseQuery(query) + end + end + req.params = params or {} + return handler(req, res, go) + end) + return server +end + +return server |