Commit b2cefdcf authored by Marek Vavruša's avatar Marek Vavruša Committed by Marek Vavruša

modules/http: allow passing server options to http configuration

This allows HTTP server to start with reuseport, reuseaddr or v6only.
The reuseport allows running HTTP module on all forks, not just the main one.
parent d0e32c6f
Pipeline #37206 passed with stages
in 8 minutes and 39 seconds
......@@ -26,8 +26,8 @@ for starters?
-- Load HTTP module with defaults
modules = {
http = {
host = 'localhost',
port = 8053,
host = 'localhost', -- Default: 'localhost'
port = 8053, -- Default: 8053
geoip = 'GeoLite2-City.mmdb' -- Optional, see
-- e.g. https://dev.maxmind.com/geoip/geoip2/geolite2/
-- and install mmdblua library
......@@ -50,8 +50,6 @@ Major drawback is that current browsers won't do HTTP/2 over insecure connection
.. code-block:: lua
http = {
host = 'localhost',
port = 8053,
cert = false,
}
......@@ -60,8 +58,6 @@ If you want to provide your own certificate and key, you're welcome to do so:
.. code-block:: lua
http = {
host = 'localhost',
port = 8053,
cert = 'mycert.crt',
key = 'mykey.key',
}
......@@ -286,10 +282,18 @@ Services exposed in the previous part share the same external interface. This me
.. code-block:: lua
http.interface('127.0.0.1', 8080, {
['/conf'] = {'application/json', function (h, stream) print('configuration API') end},
['/private'] = {'text/html', static_page},
})
http.add_interface {
endpoints = {
['/conf'] = {
'application/json', function (h, stream)
return 'configuration API\n'
end
},
},
-- Same options as the config() method
host = 'localhost',
port = '8054',
}
This way you can have different internal-facing and external-facing services at the same time.
......
-- Load dependent modules
if not stats then modules.load('stats') end
-- This is leader-only module
if worker.id > 0 then return {} end
-- This is a module that does the heavy lifting to provide an HTTP/2 enabled
-- server that supports TLS by default and provides endpoint for other modules
-- in order to enable them to export restful APIs and websocket streams.
......@@ -275,44 +272,53 @@ local function updatecert(crtfile, keyfile)
end
-- @function Listen on given HTTP(s) host
function M.interface(host, port, endpoints, crtfile, keyfile)
function M.add_interface(conf)
local crt, key, ephemeral
if crtfile ~= false then
if conf.crtfile ~= false then
-- Check if the cert file exists
if not crtfile then
crtfile = 'self.crt'
keyfile = 'self.key'
if not conf.crtfile then
conf.crtfile = 'self.crt'
conf.keyfile = 'self.key'
ephemeral = true
elseif not keyfile then
elseif not conf.keyfile then
error('certificate provided, but missing key')
end
-- Read or create self-signed x509 certificate
local f = io.open(crtfile, 'r')
local f = io.open(conf.crtfile, 'r')
if f then
crt = assert(x509.new(f:read('*all')))
f:close()
-- Continue reading key file
if crt then
f = io.open(keyfile, 'r')
f = io.open(conf.keyfile, 'r')
key = assert(pkey.new(f:read('*all')))
f:close()
end
elseif ephemeral then
crt, key = updatecert(crtfile, keyfile)
crt, key = updatecert(conf.crtfile, conf.keyfile)
end
-- Check loaded certificate
if not crt or not key then
panic('failed to load certificate "%s"', crtfile)
panic('failed to load certificate "%s"', conf.crtfile)
end
end
-- Compose server handler
local routes = route(endpoints)
local routes = route(conf.endpoints or M.endpoints)
-- Enable SO_REUSEPORT by default (unless explicitly turned off)
local reuseport = (conf.reuseport ~= nil) and conf.reuseport or true
if not reuseport and worker.id > 0 then
warn('[http] the "reuseport" option is disabled and multiple forks are used, ' ..
'port binding will fail on some instances')
end
-- Create TLS context and start listening
local s, err = http_server.listen {
cq = worker.bg_worker.cq,
host = host,
port = port,
client_timeout = 5,
host = conf.host or 'localhost',
port = conf.port or 8053,
v6only = conf.v6only,
reuseaddr = conf.reuseaddr,
reuseport = reuseport,
client_timeout = conf.client_timeout or 5,
ctx = crt and tlscontext(crt, key),
onstream = routes,
}
......@@ -321,7 +327,7 @@ function M.interface(host, port, endpoints, crtfile, keyfile)
err = select(2, s:listen())
end
if err then
panic('failed to listen on %s@%d: %s', host, port, err)
panic('failed to listen on %s@%d: %s', conf.host, conf.port, err)
end
table.insert(M.servers, s)
-- Create certificate renewal timer if ephemeral
......@@ -330,12 +336,23 @@ function M.interface(host, port, endpoints, crtfile, keyfile)
expiry = math.max(0, expiry - (os.time() - 3 * 24 * 3600))
event.after(expiry, function ()
log('[http] refreshed ephemeral certificate')
crt, key = updatecert(crtfile, keyfile)
crt, key = updatecert(conf.crtfile, conf.keyfile)
s.ctx = tlscontext(crt, key)
end)
end
end
-- @function Listen on given HTTP(s) host (backwards compatible interface)
function M.interface(host, port, endpoints, crtfile, keyfile)
return M.add_interface {
host = host,
port = port,
endpoints = endpoints,
crtfile = crtfile,
keyfile = keyfile,
}
end
-- @function Init module
function M.init()
worker.coroutine(prometheus.init)
......@@ -355,8 +372,6 @@ function M.config(conf)
if conf == true then conf = {} end
assert(type(conf) == 'table', 'config { host = "...", port = 443, cert = "...", key = "..." }')
-- Configure web interface for resolver
if not conf.port then conf.port = 8053 end
if not conf.host then conf.host = 'localhost' end
if conf.geoip then
if has_mmdb then
M.geoip = mmdb.open(conf.geoip)
......@@ -364,12 +379,7 @@ function M.config(conf)
error('[http] mmdblua library not found, please remove GeoIP configuration')
end
end
-- Add endpoints to default endpoints
local endpoints = conf.endpoints or {}
for k, v in pairs(M.endpoints) do
endpoints[k] = v
end
M.interface(conf.host, conf.port, endpoints, conf.cert, conf.key)
M.add_interface(conf)
end
return M
-- check prerequisites
local supports_http = pcall(require, 'http') and pcall(require, 'http.request')
if not supports_http then
local has_http = pcall(require, 'http') and pcall(require, 'http.request')
if not has_http then
pass('skipping http module test because its not installed')
done()
else
local request = require('http.request')
local endpoints = require('http').endpoints
-- custom endpoints
endpoints['/test'] = {'text/custom', function () return 'hello' end}
-- setup resolver
modules = {
http = {
port = 0, -- Select random port
cert = false,
endpoints = { ['/test'] = {'text/custom', function () return 'hello' end} },
endpoints = endpoints,
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment