Skip to content

Commit

Permalink
roles: introduce role for http servers
Browse files Browse the repository at this point in the history
This patch adds `roles.http_server`. This role allows to configurate one or
more HTTP servers. Those servers could be reused by several other roles.

Servers could be accessed by their names (from the config).

`get_server(name)` method returns a server by its name. If `nil` is passed,
default server is returned. The server is default, if it has
`DEFAULT_SERVER_NAME` as a name.

Closes #196
  • Loading branch information
DerekBum committed Sep 7, 2024
1 parent 0b471d8 commit f4940b8
Show file tree
Hide file tree
Showing 9 changed files with 450 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]

### Fixed

- Fixed request crash with empty body and unexpected header Content-Type (#189)

### Added

- `http_server` role to configure one or more HTTP servers (#196)

## [1.5.0] - 2023-03-29

### Added
Expand Down
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra")
string(RANDOM ALPHABET 0123456789 seed)

add_subdirectory(http)
add_subdirectory(roles)

add_custom_target(luacheck
COMMAND ${LUACHECK} ${PROJECT_SOURCE_DIR}
Expand All @@ -48,7 +49,7 @@ add_custom_target(coverage
)

if(DEFINED ENV{GITHUB_TOKEN})
set(COVERALLS_COMMAND ${LUACOVCOVERALLS} --include ^http --verbose --repo-token $ENV{GITHUB_TOKEN})
set(COVERALLS_COMMAND ${LUACOVCOVERALLS} --include ^http ^roles --verbose --repo-token $ENV{GITHUB_TOKEN})
else()
set(COVERALLS_COMMAND ${CMAKE_COMMAND} -E echo "Skipped uploading to coveralls.io: no token.")
endif()
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ http v2 revert and decisions regarding each reverted commit see
* [before\_dispatch(httpd, req)](#before_dispatchhttpd-req)
* [after\_dispatch(cx, resp)](#after_dispatchcx-resp)
* [Using a special socket](#using-a-special-socket)
* [Roles](#roles)
* [http_server](#http_server-role)
* [See also](#see-also)

## Prerequisites
Expand Down Expand Up @@ -502,6 +504,38 @@ server:start()
[socket_ref]: https://www.tarantool.io/en/doc/latest/reference/reference_lua/socket/#socket-tcp-server
## Roles
New roles could be accessed from this project.
### `http_server` role
This role can be used only with Tarantool 3.
It allows configuring one or more HTTP servers.
Those servers could be reused by several other roles.
Example of the configuration:
```yaml
roles_cfg:
roles.http_server:
default:
- listen: 8081
additional:
- listen: '127.0.0.1:8082'
```
Server address should be provided either as a URI or as a single port
(in this case, `0.0.0.0` address is used).
User can access every working HTTP server from the configuration by name,
using `get_server(name)` method.
If the `name` argument is `nil`, the default server is returned
(its name should be equal to constant `DEFAULT_SERVER_NAME`).
For an example of using this role, refer to
[this integration test](./test/integration/http_server_role_test.lua).
## See also
* [Tarantool project][Tarantool] on GitHub
Expand Down
1 change: 1 addition & 0 deletions http-scm-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ build = {
['http.version'] = 'http/version.lua',
['http.mime_types'] = 'http/mime_types.lua',
['http.codes'] = 'http/codes.lua',
['roles.http_server'] = 'roles/http_server.lua',
}
}

Expand Down
2 changes: 2 additions & 0 deletions roles/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Install
install(FILES http_server.lua DESTINATION ${TARANTOOL_INSTALL_LUADIR}/roles)
134 changes: 134 additions & 0 deletions roles/http_server.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
local urilib = require("uri")
local http_server = require('http.server')

local M = {
DEFAULT_SERVER_NAME = 'default',
}
local servers = {}

local function parse_listen(listen)
if listen == nil then
return nil, nil, "must exist"
end
if type(listen) ~= "string" and type(listen) ~= "number" then
return nil, nil, "must be a string or a number, got " .. type(listen)
end

local host
local port
if type(listen) == "string" then
local uri, err = urilib.parse(listen)
if err ~= nil then
return nil, nil, "failed to parse URI: " .. err
end

if uri.scheme ~= nil then
if uri.scheme == "unix" then
uri.unix = uri.path
else
return nil, nil, "URI scheme is not supported"
end
end

if uri.login ~= nil or uri.password then
return nil, nil, "URI login and password are not supported"
end

if uri.query ~= nil then
return nil, nil, "URI query component is not supported"
end

if uri.unix ~= nil then
host = "unix/"
port = uri.unix
else
if uri.service == nil then
return nil, nil, "URI must contain a port"
end

port = tonumber(uri.service)
if port == nil then
return nil, nil, "URI port must be a number"
end
if uri.host ~= nil then
host = uri.host
elseif uri.ipv4 ~= nil then
host = uri.ipv4
elseif uri.ipv6 ~= nil then
host = uri.ipv6
else
host = "0.0.0.0"
end
end
elseif type(listen) == "number" then
host = "0.0.0.0"
port = listen
end

if type(port) == "number" and (port < 1 or port > 65535) then
return nil, nil, "port must be in the range [1, 65535]"
end
return host, port, nil
end

local function apply_http(name, node)
local host, port, err = parse_listen(node.listen)
if err ~= nil then
error("failed to parse URI: " .. err)
end

if servers[name] == nil then
local httpd = http_server.new(host, port)
httpd:start()
servers[name] = {
httpd = httpd,
routes = {},
}
end
end

M.validate = function(conf)
if conf ~= nil and type(conf) ~= "table" then
error("configuration must be a table, got " .. type(conf))
end
conf = conf or {}

for name, node in pairs(conf) do
if type(name) ~= 'string' then
error("name of the server must be a string")
end

local _, _, err = parse_listen(node.listen)
if err ~= nil then
error("failed to parse http 'listen' param: " .. err)
end
end
end

M.apply = function(conf)
-- This should be called on the role's lifecycle, but it's better to give
-- a meaningful error if something goes wrong.
M.validate(conf)

for name, node in pairs(conf or {}) do
apply_http(name, node)
end
end

M.stop = function()
for _, server in pairs(servers) do
server.httpd:stop()
end
servers = {}
end

M.get_server = function(name)
checks('?string')

name = name or M.DEFAULT_SERVER_NAME
if type(name) == 'string' then
return servers[name]
end
end

return M
74 changes: 74 additions & 0 deletions test/integration/http_server_role_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
local t = require('luatest')
local treegen = require('luatest.treegen')
local server = require('luatest.server')
local fun = require('fun')
local yaml = require('yaml')

local http_server_role = require('roles.http_server')
local mock_role = require('test.mocks.mock_role')

local g = t.group()

g.test_http_server_role_usage = function()
local config = {
credentials = {
users = {
guest = {
roles = {'super'},
},
},
},
iproto = {
listen = {{uri = 'unix/:./{{ instance_name }}.iproto'}},
},
groups = {
['group-001'] = {
replicasets = {
['replicaset-001'] = {
instances = {
['instance-001'] = {},
},
},
},
},
},
roles_cfg = {
http_server = {
default = {
listen = 13000,
},
additional = {
listen = 13001,
}
},
mock_role = {
{
id = 1,
},
{
id = 2,
name = 'additional',
},
},
},
}
local dir = treegen.prepare_directory({}, {})

local config_file = treegen.write_file(dir, 'config.yaml',
yaml.encode(config))
local opts = {config_file = config_file, chdir = dir}
g.server = server:new(fun.chain(opts, {alias = 'instance-001'}):tomap())
g.server:start()

t.assert_equals(g.server:eval('return box.info.name'), g.server.alias)

-- Verify that the default database mode for a singleton
-- instance (the only one in its replicaset) is read-write.
t.assert_equals(g.server:eval('return box.info.ro'), false)

http_server_role.apply(config.roles_cfg.http_server)
mock_role.apply(config.roles_cfg.mock_role, http_server_role)

t.assert_equals(mock_role.get_server_port(1), 13000)
t.assert_equals(mock_role.get_server_port(2), 13001)
end
15 changes: 15 additions & 0 deletions test/mocks/mock_role.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
local M = {}

local servers = {}

M.apply = function(conf, http_server_role)
for _, server in pairs(conf) do
servers[server.id] = http_server_role.get_server(server.name)
end
end

M.get_server_port = function(id)
return servers[id].httpd.port
end

return M
Loading

0 comments on commit f4940b8

Please sign in to comment.