Skip to content

Commit c600e4f

Browse files
committed
roles: introduce role for http servers
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
1 parent 0b471d8 commit c600e4f

13 files changed

+479
-11
lines changed

.github/workflows/test.yml

+12-2
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,28 @@ jobs:
1010
strategy:
1111
fail-fast: false
1212
matrix:
13-
tarantool: ['1.10', '2.5', '2.6', '2.7', '2.8']
13+
tarantool: ['1.10', '2.5', '2.6', '2.7', '2.8', '3.2']
1414
coveralls: [false]
1515
include:
1616
- tarantool: '2.10'
1717
coveralls: true
1818
runs-on: [ubuntu-20.04]
1919
steps:
2020
- uses: actions/checkout@master
21-
- uses: tarantool/setup-tarantool@v1
21+
- uses: tarantool/setup-tarantool@v3
2222
with:
2323
tarantool-version: ${{ matrix.tarantool }}
2424

25+
- name: Prepare the repo
26+
run: curl -L https://tarantool.io/release/2/installer.sh | bash
27+
env:
28+
DEBIAN_FRONTEND: noninteractive
29+
30+
- name: Install tt cli
31+
run: sudo apt install -y tt=2.4.0
32+
env:
33+
DEBIAN_FRONTEND: noninteractive
34+
2535
- name: Cache rocks
2636
uses: actions/cache@v2
2737
id: cache-rocks

.luacheckrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
std = "luajit"
2-
globals = {"box", "_TARANTOOL", "tonumber64", "utf8"}
2+
globals = {"box", "_TARANTOOL", "tonumber64", "utf8", "checks"}
33
ignore = {
44
-- Accessing an undefined field of a global variable <debug>.
55
"143/debug",

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77
## [Unreleased]
88

99
### Fixed
10+
1011
- Fixed request crash with empty body and unexpected header Content-Type (#189)
1112

13+
### Added
14+
15+
- `http_server` role to configure one or more HTTP servers (#196)
16+
1217
## [1.5.0] - 2023-03-29
1318

1419
### Added

CMakeLists.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra")
2525
string(RANDOM ALPHABET 0123456789 seed)
2626

2727
add_subdirectory(http)
28+
add_subdirectory(roles)
2829

2930
add_custom_target(luacheck
3031
COMMAND ${LUACHECK} ${PROJECT_SOURCE_DIR}
@@ -48,7 +49,7 @@ add_custom_target(coverage
4849
)
4950

5051
if(DEFINED ENV{GITHUB_TOKEN})
51-
set(COVERALLS_COMMAND ${LUACOVCOVERALLS} --include ^http --verbose --repo-token $ENV{GITHUB_TOKEN})
52+
set(COVERALLS_COMMAND ${LUACOVCOVERALLS} --include ^http ^roles --verbose --repo-token $ENV{GITHUB_TOKEN})
5253
else()
5354
set(COVERALLS_COMMAND ${CMAKE_COMMAND} -E echo "Skipped uploading to coveralls.io: no token.")
5455
endif()

README.md

+34
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ http v2 revert and decisions regarding each reverted commit see
4848
* [before\_dispatch(httpd, req)](#before_dispatchhttpd-req)
4949
* [after\_dispatch(cx, resp)](#after_dispatchcx-resp)
5050
* [Using a special socket](#using-a-special-socket)
51+
* [Roles](#roles)
52+
* [http_server](#http_server-role)
5153
* [See also](#see-also)
5254

5355
## Prerequisites
@@ -502,6 +504,38 @@ server:start()
502504

503505
[socket_ref]: https://www.tarantool.io/en/doc/latest/reference/reference_lua/socket/#socket-tcp-server
504506

507+
## Roles
508+
509+
New roles could be accessed from this project.
510+
511+
### `http_server` role
512+
513+
This role can be used only with Tarantool 3.
514+
It allows configuring one or more HTTP servers.
515+
Those servers could be reused by several other roles.
516+
517+
Example of the configuration:
518+
519+
```yaml
520+
roles_cfg:
521+
roles.http_server:
522+
default:
523+
- listen: 8081
524+
additional:
525+
- listen: '127.0.0.1:8082'
526+
```
527+
528+
Server address should be provided either as a URI or as a single port
529+
(in this case, `0.0.0.0` address is used).
530+
531+
User can access every working HTTP server from the configuration by name,
532+
using `get_server(name)` method.
533+
If the `name` argument is `nil`, the default server is returned
534+
(its name should be equal to constant `DEFAULT_SERVER_NAME`).
535+
536+
For an example of using this role, refer to
537+
[this integration test](./test/integration/http_server_role_test.lua).
538+
505539
## See also
506540

507541
* [Tarantool project][Tarantool] on GitHub

deps.sh

+7-7
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
set -e
66

77
# Test dependencies:
8-
tarantoolctl rocks install luatest 0.5.7
9-
tarantoolctl rocks install luacheck 0.25.0
10-
tarantoolctl rocks install luacov 0.13.0
11-
tarantoolctl rocks install luafilesystem 1.7.0-2
8+
tt rocks install luatest
9+
tt rocks install luacheck 0.25.0
10+
tt rocks install luacov 0.13.0
11+
tt rocks install luafilesystem 1.7.0-2
1212

1313
# cluacov, luacov-coveralls and dependencies
14-
tarantoolctl rocks install luacov-coveralls 0.2.3-1 --server=https://luarocks.org
15-
tarantoolctl rocks install cluacov 0.1.2-1 --server=https://luarocks.org
14+
tt rocks install luacov-coveralls 0.2.3-1 --server=https://luarocks.org
15+
tt rocks install cluacov 0.1.2-1 --server=https://luarocks.org
1616

17-
tarantoolctl rocks make
17+
tt rocks make

http-scm-1.rockspec

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ build = {
3131
['http.version'] = 'http/version.lua',
3232
['http.mime_types'] = 'http/mime_types.lua',
3333
['http.codes'] = 'http/codes.lua',
34+
['roles.http_server'] = 'roles/http_server.lua',
3435
}
3536
}
3637

roles/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Install
2+
install(FILES http_server.lua DESTINATION ${TARANTOOL_INSTALL_LUADIR}/roles)

roles/http_server.lua

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
local urilib = require("uri")
2+
local http_server = require('http.server')
3+
4+
local M = {
5+
DEFAULT_SERVER_NAME = 'default',
6+
}
7+
local servers = {}
8+
9+
local function parse_listen(listen)
10+
if listen == nil then
11+
return nil, nil, "must exist"
12+
end
13+
if type(listen) ~= "string" and type(listen) ~= "number" then
14+
return nil, nil, "must be a string or a number, got " .. type(listen)
15+
end
16+
17+
local host
18+
local port
19+
if type(listen) == "string" then
20+
local uri, err = urilib.parse(listen)
21+
if err ~= nil then
22+
return nil, nil, "failed to parse URI: " .. err
23+
end
24+
25+
if uri.scheme ~= nil then
26+
if uri.scheme == "unix" then
27+
uri.unix = uri.path
28+
else
29+
return nil, nil, "URI scheme is not supported"
30+
end
31+
end
32+
33+
if uri.login ~= nil or uri.password then
34+
return nil, nil, "URI login and password are not supported"
35+
end
36+
37+
if uri.query ~= nil then
38+
return nil, nil, "URI query component is not supported"
39+
end
40+
41+
if uri.unix ~= nil then
42+
host = "unix/"
43+
port = uri.unix
44+
else
45+
if uri.service == nil then
46+
return nil, nil, "URI must contain a port"
47+
end
48+
49+
port = tonumber(uri.service)
50+
if port == nil then
51+
return nil, nil, "URI port must be a number"
52+
end
53+
if uri.host ~= nil then
54+
host = uri.host
55+
elseif uri.ipv4 ~= nil then
56+
host = uri.ipv4
57+
elseif uri.ipv6 ~= nil then
58+
host = uri.ipv6
59+
else
60+
host = "0.0.0.0"
61+
end
62+
end
63+
elseif type(listen) == "number" then
64+
host = "0.0.0.0"
65+
port = listen
66+
end
67+
68+
if type(port) == "number" and (port < 1 or port > 65535) then
69+
return nil, nil, "port must be in the range [1, 65535]"
70+
end
71+
return host, port, nil
72+
end
73+
74+
local function apply_http(name, node)
75+
local host, port, err = parse_listen(node.listen)
76+
if err ~= nil then
77+
error("failed to parse URI: " .. err)
78+
end
79+
80+
if servers[name] == nil then
81+
local httpd = http_server.new(host, port)
82+
httpd:start()
83+
servers[name] = {
84+
httpd = httpd,
85+
routes = {},
86+
}
87+
end
88+
end
89+
90+
M.validate = function(conf)
91+
if conf ~= nil and type(conf) ~= "table" then
92+
error("configuration must be a table, got " .. type(conf))
93+
end
94+
conf = conf or {}
95+
96+
for name, node in pairs(conf) do
97+
if type(name) ~= 'string' then
98+
error("name of the server must be a string")
99+
end
100+
101+
local _, _, err = parse_listen(node.listen)
102+
if err ~= nil then
103+
error("failed to parse http 'listen' param: " .. err)
104+
end
105+
end
106+
end
107+
108+
M.apply = function(conf)
109+
-- This should be called on the role's lifecycle, but it's better to give
110+
-- a meaningful error if something goes wrong.
111+
M.validate(conf)
112+
113+
for name, node in pairs(conf or {}) do
114+
apply_http(name, node)
115+
end
116+
end
117+
118+
M.stop = function()
119+
for _, server in pairs(servers) do
120+
server.httpd:stop()
121+
end
122+
servers = {}
123+
end
124+
125+
M.get_server = function(name)
126+
checks('?string')
127+
128+
name = name or M.DEFAULT_SERVER_NAME
129+
if type(name) == 'string' then
130+
return servers[name]
131+
end
132+
end
133+
134+
return M

test/helpers.lua

+12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ local socket = require('socket')
44

55
local helpers = table.copy(require('luatest').helpers)
66

7+
local luatest = require('luatest')
8+
local luatest_utils = require('luatest.utils')
9+
710
helpers.base_port = 12345
811
helpers.base_host = '127.0.0.1'
912
helpers.base_uri = ('http://%s:%s'):format(helpers.base_host, helpers.base_port)
@@ -101,4 +104,13 @@ helpers.teardown = function(httpd)
101104
end)
102105
end
103106

107+
helpers.is_tarantool3 = function()
108+
local tarantool_version = luatest_utils.get_tarantool_version()
109+
return luatest_utils.version_ge(tarantool_version, luatest_utils.version(3, 0, 0))
110+
end
111+
112+
helpers.skip_if_not_tarantool3 = function()
113+
luatest.skip_if(not helpers.is_tarantool3(), 'Only Tarantool 3 is supported')
114+
end
115+
104116
return helpers
+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
local t = require('luatest')
2+
local treegen = require('luatest.treegen')
3+
local server = require('luatest.server')
4+
local fun = require('fun')
5+
local yaml = require('yaml')
6+
7+
local helpers = require('test.helpers')
8+
local http_server_role = require('roles.http_server')
9+
local mock_role = require('test.mocks.mock_role')
10+
11+
local g = t.group()
12+
13+
g.test_http_server_role_usage = function()
14+
helpers.skip_if_not_tarantool3()
15+
16+
local config = {
17+
credentials = {
18+
users = {
19+
guest = {
20+
roles = {'super'},
21+
},
22+
},
23+
},
24+
iproto = {
25+
listen = {{uri = 'unix/:./{{ instance_name }}.iproto'}},
26+
},
27+
groups = {
28+
['group-001'] = {
29+
replicasets = {
30+
['replicaset-001'] = {
31+
instances = {
32+
['instance-001'] = {},
33+
},
34+
},
35+
},
36+
},
37+
},
38+
roles_cfg = {
39+
http_server = {
40+
default = {
41+
listen = 13000,
42+
},
43+
additional = {
44+
listen = 13001,
45+
}
46+
},
47+
mock_role = {
48+
{
49+
id = 1,
50+
},
51+
{
52+
id = 2,
53+
name = 'additional',
54+
},
55+
},
56+
},
57+
}
58+
local dir = treegen.prepare_directory({}, {})
59+
60+
local config_file = treegen.write_file(dir, 'config.yaml',
61+
yaml.encode(config))
62+
local opts = {config_file = config_file, chdir = dir}
63+
g.server = server:new(fun.chain(opts, {alias = 'instance-001'}):tomap())
64+
g.server:start()
65+
66+
http_server_role.apply(config.roles_cfg.http_server)
67+
mock_role.apply(config.roles_cfg.mock_role, http_server_role)
68+
69+
t.assert_equals(mock_role.get_server_port(1), 13000)
70+
t.assert_equals(mock_role.get_server_port(2), 13001)
71+
end

test/mocks/mock_role.lua

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
local M = {}
2+
3+
local servers = {}
4+
5+
M.apply = function(conf, http_server_role)
6+
for _, server in pairs(conf) do
7+
servers[server.id] = http_server_role.get_server(server.name)
8+
end
9+
end
10+
11+
M.get_server_port = function(id)
12+
return servers[id].httpd.port
13+
end
14+
15+
return M

0 commit comments

Comments
 (0)