summaryrefslogtreecommitdiffstatshomepage
path: root/plugins/weblit/app.lua
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/weblit/app.lua')
-rw-r--r--plugins/weblit/app.lua261
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