summaryrefslogtreecommitdiff
path: root/web/server.lua
diff options
context:
space:
mode:
Diffstat (limited to 'web/server.lua')
-rw-r--r--web/server.lua114
1 files changed, 114 insertions, 0 deletions
diff --git a/web/server.lua b/web/server.lua
new file mode 100644
index 0000000..a0cc505
--- /dev/null
+++ b/web/server.lua
@@ -0,0 +1,114 @@
+local cqueues = require 'cqueues'
+local cq_condition = require'cqueues.condition'
+local http_server = require 'http.server'
+local http_headers = require 'http.headers'
+local http_util = require 'http.util'
+local http_cookie = require 'http.cookie'
+local pprint=require'pprint'
+
+local function make_req(app, server, stream)
+ local req = {server=server, stream=stream, app=app}
+ req.headers = assert(req.stream:get_headers())
+ req.method = req.headers:get":method"
+
+ local path = req.headers:get":path"
+ local sel, qs = path:match"^(.-)%?(.*)$"
+ if sel then path = sel end
+ req.path, req.qs = path, qs
+ req.args = {}
+ if req.qs then
+ for k,v in http_util.query_args(req.qs) do req.args[k] = v end
+ end
+
+ req.cookies = assert(http_cookie.parse_cookies(req.headers))
+
+ if req.method == 'POST' then
+ req.form = {}
+ local content_type = req.headers:get'content-type'
+ local is_form = content_type:match'^application/x%-www%-form%-urlencoded'
+ if is_form then
+ local body = stream:get_body_as_string()
+ -- i'm like 96% sure this is correct
+ body = body:gsub('+',' ')
+ for k,v in http_util.query_args(body) do
+ req.form[k] = v
+ end
+ end
+ end
+
+ req.app:run_hooks('pre_req',req)
+ return req
+end
+
+-- take return values of view function, construct and send http response
+local function make_resp(req, body, header_dict, status_code)
+ if body == false then return end -- assume view sent its own response
+
+ local rheaders
+ if rawequal(getmetatable(header_dict), http_headers.mt) then
+ rheaders = header_dict
+ else
+ rheaders = http_headers.new()
+ for k,v in pairs(header_dict or {}) do
+ rheaders:append(k:gsub("_","-"), v)
+ end
+ if not rheaders:has':status' then
+ rheaders:append(':status',tostring(status_code or 200)) end
+ if not rheaders:has'content-type' then
+ rheaders:append('content-type','text/html') end
+ end
+
+ req.app:run_hooks('post_req',req,body,rheaders,status_code)
+
+ assert(req.stream:write_headers(rheaders, false))
+
+ if type(body) == 'string' then
+ assert(req.stream:write_body_from_string(body))
+ elseif io.type(body) == 'file' then
+ assert(req.stream:write_body_from_file(body))
+ body:close()
+ elseif type(body) == 'table' and getmetatable(body).__tostring then
+ assert(req.stream:write_body_from_string(tostring(body)))
+ else
+ error('unsupported response type: '..type(body))
+ end -- todo maybe: chunk iterators, if i need them
+end
+
+local function handle_request(app, server, stream)
+ local req = make_req(app, server, stream)
+ print('handling',app.version,req.method,req.path)
+ local ok, body, headers, status = xpcall(app.view, debug.traceback, req)
+ if not ok then
+ if type(body)=='table' and body.fail then
+ status,headers = body.status, nil
+ body = '<h1>'..status..' page</h1><pre>'..body.tb..'</pre>'
+ else
+ status, headers = 500, nil
+ body = '<h1>500 internal server explosion</h1><pre>'..body..'</pre>'
+ end
+ end
+ return make_resp(req, body, headers, status)
+end
+
+local function onerror(server, ctx, op, err, errno)
+ local msg = op .. " on " .. tostring(ctx) .. " failed"
+ if err then msg = msg.. ": " .. tostring(err) end
+ io.stderr:write(msg, "\n")
+end
+
+local function run(cq, app)
+ local server = assert(http_server.listen {
+ cq = cq, host = '0.0.0.0', port = 8082, onerror = onerror,
+ onstream = function(server, stream)
+ local ok, err = xpcall(handle_request, debug.traceback, app, server, stream)
+ if not ok then error(err, 0) end
+ end
+ })
+ assert(server:listen())
+ local _,addr,port = server:localname()
+ print(("listening on http://%s:%s"):format(addr,port))
+end
+
+return {
+ run = run,
+}