Skip to content

Commit 977a618

Browse files
DerekBumoleg-jukovec
authored andcommitted
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` (`"default"`) as a name. Closes #196
1 parent 0b471d8 commit 977a618

File tree

13 files changed

+596
-15
lines changed

13 files changed

+596
-15
lines changed

.github/workflows/test.yml

Lines changed: 16 additions & 5 deletions
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.10', '2.11', '3.1', '3.2']
1414
coveralls: [false]
1515
include:
16-
- tarantool: '2.10'
16+
- tarantool: '2.11'
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
@@ -40,8 +50,9 @@ jobs:
4050
env:
4151
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4252

43-
- name: Run tests and code coverage analysis
44-
run: make -C build coverage
53+
- name: Run tests without code coverage analysis
54+
run: make -C build luatest
55+
if: matrix.coveralls != true
4556

4657
- name: Send code coverage to coveralls.io
4758
run: make -C build coveralls

.luacheckrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 5 additions & 0 deletions
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+
- `roles.httpd` role to configure one or more HTTP servers (#196)
16+
1217
## [1.5.0] - 2023-03-29
1318

1419
### Added

CMakeLists.txt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,25 @@ 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}
3132
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
3233
COMMENT "Run luacheck"
3334
)
3435

35-
add_custom_target(luatest
36+
add_custom_target(luatest-coverage
3637
COMMAND ${LUATEST} -v --coverage --shuffle all:${seed}
3738
BYPRODUCTS ${CODE_COVERAGE_STATS}
3839
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
39-
COMMENT "Run regression tests"
40+
COMMENT "Run regression tests with coverage"
41+
)
42+
43+
add_custom_target(luatest
44+
COMMAND ${LUATEST} -v --shuffle all:${seed}
45+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
46+
COMMENT "Run regression tests without coverage"
4047
)
4148

4249
add_custom_target(coverage
@@ -65,4 +72,4 @@ add_custom_target(coveralls
6572

6673
set (LUA_PATH "LUA_PATH=${PROJECT_SOURCE_DIR}/?.lua\\;${PROJECT_SOURCE_DIR}/?/init.lua\\;\\;")
6774
set (LUA_SOURCE_DIR "LUA_SOURCE_DIR=${PROJECT_SOURCE_DIR}")
68-
set_target_properties(luatest PROPERTIES ENVIRONMENT "${LUA_PATH};${LUA_SOURCE_DIR}")
75+
set_target_properties(luatest-coverage PROPERTIES ENVIRONMENT "${LUA_PATH};${LUA_SOURCE_DIR}")

README.md

Lines changed: 110 additions & 0 deletions
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+
* [roles.httpd](#roleshttpd)
5153
* [See also](#see-also)
5254

5355
## Prerequisites
@@ -502,6 +504,114 @@ 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+
Tarantool 3 roles could be accessed from this project.
510+
511+
### `roles.httpd`
512+
513+
It allows configuring one or more HTTP servers. Those servers could be reused
514+
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 `require('roles.httpd').get_server(name)` method.
532+
If the `name` argument is `nil`, the default server is returned
533+
(its name should be equal to constant
534+
`require('roles.httpd').DEFAULT_SERVER_NAME`, which is `"default"`).
535+
536+
Let's look at the example of using this role. Consider a new role
537+
`roles/hello_world.lua`:
538+
```lua
539+
local M = { dependencies = { 'roles.httpd' } }
540+
local server = {}
541+
542+
M.validate = function(conf)
543+
if conf == nil or conf.httpd == nil then
544+
error("httpd must be set")
545+
end
546+
local server = require('roles.httpd').get_server(conf.httpd)
547+
if server == nil then
548+
error("the httpd server " .. conf.httpd .. " not found")
549+
end
550+
end
551+
552+
M.apply = function(conf)
553+
server = require('roles.httpd').get_server(conf.httpd)
554+
555+
server:route({
556+
path = '/hello/world',
557+
name = 'greeting',
558+
}, function(tx)
559+
return tx:render({text = 'Hello, world!'})
560+
end)
561+
end
562+
563+
M.stop = function()
564+
server:delete('greeting')
565+
end
566+
567+
return M
568+
```
569+
570+
This role accepts a server by name from a config and creates a route to return
571+
`Hello, world!` to every request by this route.
572+
573+
Then we need to write a simple config to start the Tarantool instance via
574+
`tt`:
575+
```yaml
576+
app:
577+
file: 'myapp.lua'
578+
579+
groups:
580+
group001:
581+
replicasets:
582+
replicaset001:
583+
roles: [roles.httpd, roles.hello_world]
584+
roles_cfg:
585+
roles.httpd:
586+
default:
587+
listen: 8081
588+
additional:
589+
listen: '127.0.0.1:8082'
590+
roles.hello_world:
591+
httpd: 'additional'
592+
instances:
593+
instance001:
594+
iproto:
595+
listen:
596+
- uri: '127.0.0.1:3301'
597+
```
598+
599+
Next step, we need to start this instance using `tt start`:
600+
```bash
601+
$ tt start
602+
• Starting an instance [app:instance001]...
603+
$ tt status
604+
INSTANCE STATUS PID MODE
605+
app:instance001 RUNNING 2499387 RW
606+
```
607+
608+
And then, we can get the greeting by running a simple curl command from a
609+
terminal:
610+
```bash
611+
$ curl http://127.0.0.1:8082/hello/world
612+
Hello, world!
613+
```
614+
505615
## See also
506616

507617
* [Tarantool project][Tarantool] on GitHub

deps.sh

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
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+
# Could be replaced with luatest >= 1.1.0 after a release.
9+
tt rocks install luatest
10+
tt rocks install luacheck 0.25.0
11+
tt rocks install luacov 0.13.0
12+
tt rocks install luafilesystem 1.7.0-2
1213

1314
# 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
15+
tt rocks install luacov-coveralls 0.2.3-1 --server=https://luarocks.org
16+
tt rocks install cluacov 0.1.2-1 --server=https://luarocks.org
1617

17-
tarantoolctl rocks make
18+
tt rocks make

http-scm-1.rockspec

Lines changed: 1 addition & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

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

0 commit comments

Comments
 (0)