nepenthes/core/microdyne.lua

218 lines
4.7 KiB
Lua
Executable file

#!/usr/bin/env lua5.4
if os.getenv('LUA_APP_BOOTSTRAP') then
dofile(os.getenv('LUA_APP_BOOTSTRAP'))
end
pcall(require, "luarocks.loader")
local http_server = require 'http.server'
local http_headers = require 'http.headers'
local unix = require 'unix'
local daemonize = require 'daemonparts.daemonize'
local config = require 'components.config'
local output = require 'daemonparts.output'
local corewait = require 'daemonparts.corewait'
if not arg[1] then
error("Provide application")
end
if not arg[2] then
error("Provide config file")
end
local location = unix.getcwd()
package.path = package.path .. ';' .. location .. '/?.lua'
local app
local app_f = assert(loadfile( arg[1] ))
config( arg[2] )
if config.log_level then
output.filter( config.log_level )
end
local function header_cleanup( var )
return 'HTTP_' .. var:upper():gsub("%-", '_')
end
local function http_responder( server, stream ) -- luacheck: ignore 212
local req_headers = stream:get_headers()
local cl_family, cl_addr, cl_port = stream:peername() -- luacheck: ignore 211
local path = req_headers:get(':path')
local request = {
SERVER_NAME = req_headers:get(':authority'),
SERVER_SOFTWARE = 'lua-http',
SERVER_PROTOCOL = stream.connection.version,
--SERVER_PORT = config.http_port,
REQUEST_METHOD = req_headers:get(':method'),
DOCUMENT_ROOT = "/", -- XXX: Set correctly
PATH_INFO = path,
PATH_TRANSLATED = path, -- XXX: Set Correctly
APP_PATH = '/', -- XXX: Set Correctly
SCRIPT_NAME = '/', -- XXX: what should this be?
QUERY_STRING = "?", -- XXX: Needs to be parsed
REMOTE_ADDR = cl_addr,
REMOTE_PORT = cl_port,
REMOTE_USER = req_headers:get('authorization'), -- XXX: parse this
CONTENT_TYPE = req_headers:get('content-type'),
CONTENT_LENGTH = req_headers:get('content-length'),
HTTP_COOKIE = req_headers:get('cookie')
}
-- XXX: I'm sorry
request[ header_cleanup('X_USER_AGENT') ] = req_headers:get('user-agent')
-- import all nonstandard headers; they're important
for name, val in req_headers:each() do
--print(name, val)
if name:match('^[Xx]%-') then
request[ header_cleanup( name) ] = val
end
end
-- X-forwarded-for or similar?
if config.real_ip_header then
local real_ip = request[header_cleanup(config.real_ip_header)]
if real_ip then
request.REMOTE_ADDR = real_ip
end
end
request.input = stream:get_body_as_file()
--
-- Call WSAPI application here
--
local rawstatus, wsapi_headers, iter = app.run( request )
-- XXX: This is an ugly way to do this, would be better to fix
-- Perihelion maybe? I think that it's a successor project problem.
local clean_headers = {}
for k, v in pairs(wsapi_headers) do
local lk = k:lower()
if lk == 'content-type'
and v:lower() == 'text/html' then
clean_headers[lk] = 'text/html; charset=utf-8'
else
clean_headers[lk] = v
end
end
local status
if type(rawstatus) == 'string' then
status = rawstatus:match("^(%d+)")
else
status = rawstatus
end
local res_headers = http_headers.new()
res_headers:append("Server", config.server_software or 'nginx')
res_headers:append(":status", status)
for k, v in pairs(clean_headers) do
res_headers:append(k, v)
end
stream:write_headers(res_headers, false)
for chunk in iter do
stream:write_chunk(chunk, false)
end
stream:write_chunk("", true)
output.info(string.format("Web: %s [%s %s] %s",
request.REMOTE_ADDR,
request.REQUEST_METHOD,
request.PATH_INFO,
rawstatus
))
end
local cq
local server
local function startup()
cq = corewait.single()
if config.nochdir then
unix.chdir(location)
end
if config.pidfile then
daemonize.pidfile( config.pidfile )
end
app = app_f()
local args = {
host = config.http_host,
port = math.floor(config.http_port),
onstream = http_responder,
tls = false,
cq = cq.cq
}
if config.unix_socket then
unix.unlink( config.unix_socket )
args.host = nil
args.port = nil
args.path = config.unix_socket
end
server = assert(http_server.listen(args))
cq:wrap(function()
cq:poll()
output.info("Stop Signal Recieved")
server:close()
if app.shutdown_hook then
pcall(app.shutdown_hook)
end
end)
cq:enable_signal_stop()
assert(server:listen())
end
if config.daemonize then
daemonize.go( startup )
output.switch('syslog', 'user', arg[1])
output.filter( config.log_level )
else
output.info("Remaining in foreground")
startup()
end
output.notice("Startup HTTP:", config.http_host, config.http_port)
local last_err = cq:monotime()
local err_count = 0
for err in cq:errors() do
output.error(err)
if last_err ~= cq:monotime() then
last_err = cq:monotime()
err_count = 0
else
err_count = err_count + 1
if err_count > 10 then
os.exit(10)
end
end
end
if config.unix_socket then
unix.unlink( config.unix_socket )
end