1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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,
}
|