Skip to content

Commit 502f76f

Browse files
committed
roles: introduce role for http servers
This patch adds `roles.httpd`. This role allows to configure 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 502f76f

13 files changed

+545
-12
lines changed

.github/workflows/test.yml

+13-3
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', '2.10']
1414
coveralls: [false]
1515
include:
16-
- tarantool: '2.10'
16+
- tarantool: '3.2'
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

+3-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",
@@ -9,6 +9,8 @@ ignore = {
99
"143/string",
1010
-- Accessing an undefined field of a global variable <table>.
1111
"143/table",
12+
-- Accessing an undefined field of a global variable <package>.
13+
"143/package",
1214
-- Unused argument <self>.
1315
"212/self",
1416
-- Redefining a local variable.

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+
- `httpd` 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

+87
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+
* [httpd](#httpd-role)
5153
* [See also](#see-also)
5254

5355
## Prerequisites
@@ -502,6 +504,91 @@ 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+
### `httpd` role
512+
513+
This role can be used only with Tarantool 3. It allows configuring one or more
514+
HTTP servers. Those servers could be reused by several other roles.
515+
516+
Example of the configuration:
517+
518+
```yaml
519+
roles_cfg:
520+
roles.httpd:
521+
default:
522+
- listen: 8081
523+
additional:
524+
- listen: '127.0.0.1:8082'
525+
```
526+
527+
Server address should be provided either as a URI or as a single port
528+
(in this case, `0.0.0.0` address is used).
529+
530+
User can access every working HTTP server from the configuration by name,
531+
using `get_server(name)` method.
532+
If the `name` argument is `nil`, the default server is returned
533+
(its name should be equal to constant `DEFAULT_SERVER_NAME`).
534+
535+
Let's look at the example of using this role. Consider a new role `hello_world`:
536+
```lua
537+
local http_client = require('http.client')
538+
539+
local M = {}
540+
local server = {}
541+
542+
M.apply = function(conf, http_server_role)
543+
server = http_server_role.get_server(conf.httpd).httpd
544+
545+
server:route({
546+
path = '/hello/world'
547+
}, function(tx)
548+
return tx:render({text = 'Hello, world!'})
549+
end)
550+
end
551+
552+
M.get = function()
553+
local server_uri = ('http://%s:%s'):format(server.host, server.port)
554+
return http_client.get(server_uri .. '/hello/world')
555+
end
556+
557+
return M
558+
```
559+
560+
This role accepts a server by name from a config and creates a route to return
561+
`Hello, world!` to every request by this route.
562+
563+
Config for `httpd` and `hello_world` might look like this:
564+
```yaml
565+
roles_cfg = {
566+
httpd = {
567+
default = {
568+
listen = 8081,
569+
},
570+
additional = {
571+
listen = '127.0.0.1:8082',
572+
}
573+
},
574+
hello_world = {
575+
httpd = 'additional',
576+
},
577+
}
578+
```
579+
580+
After that, we can get `Hello, world!` by just running this script on a
581+
Tarantool instance:
582+
```lua
583+
local httpd_role = require('roles.httpd')
584+
local hello_role = require('roles.hello_world')
585+
586+
httpd_role.apply(config.roles_cfg.httpd)
587+
hello_role.apply(config.roles_cfg.hello_world, httpd_role)
588+
589+
print(hello_role.get().body)
590+
```
591+
505592
## See also
506593

507594
* [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.httpd'] = 'roles/httpd.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 httpd.lua DESTINATION ${TARANTOOL_INSTALL_LUADIR}/roles)

roles/httpd.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

0 commit comments

Comments
 (0)