From 6c5d5fec129a29c6250b094f46881cfa5aaadcc7 Mon Sep 17 00:00:00 2001 From: rebecca Date: Thu, 17 Sep 2026 20:54:44 +0100 Subject: web: import from m1msq 15b348f --- web/server.lua | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 web/server.lua (limited to 'web/server.lua') 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 = '

'..status..' page

'..body.tb..'
' + else + status, headers = 500, nil + body = '

500 internal server explosion

'..body..'
' + 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, +} -- cgit v1.2.3