Nginx proxy with Tarantool module
8.7K
Key features:
See more about:
Tarantool NginX upstream module: https://hub.docker.com/r/tarantool/tarantool-nginx
Tarantool: https://hub.docker.com/r/tarantool/tarantool
git clone https://github.com/tarantool/nginx_upstream_module.git nginx_upstream_module
cd nginx_upstream_module
git submodule update --init --recursive
git clone https://github.com/nginx/nginx.git nginx
# Ubuntu
apt-get install libpcre++0 gcc unzip libpcre3-dev zlib1g-dev libssl-dev libxslt-dev
make build-all
Requirements (for details, see REPO_ROOT/Makefile)
libyajl >= 2.0(https://lloyd.github.io/yajl/)
libmsgpuck >= 2.0 (https://github.com/rtsisyk/msgpuck)
$ ./configure --add-module=REPO_ROOT && make
brew tap denji/nginx
brew install nginx-full --with-tarantool-module
## Typical configuration, for more see http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream
upstream backend {
server 127.0.0.1:9999 max_fails=1 fail_timeout=30s;
server 127.0.0.1:10000;
# ...
server 127.0.0.1:10001 backup;
# ...
}
server {
location = /tnt {
tnt_pass backend;
}
}
Note: since v0.2.0
With this module, you can call Tarantool stored procedures via HTTP REST methods (GET, POST, PUT, PATCH, DELETE).
Example:
upstream backend {
# Tarantool hosts
server 127.0.0.1:9999;
}
server {
# HTTP [GET | POST | PUT | PATCH | DELETE] /tnt_rest?q=1&q=2&q=3
location /tnt_rest {
# REST mode on
tnt_http_rest_methods get post put patch delete; # or all
# Pass http headers and uri
tnt_pass_http_request on;
# Module on
tnt_pass backend;
}
}
-- Tarantool procedure
function tnt_rest(req)
req.headers -- http headers
req.uri -- uri
return { 'ok' }
end
$> wget NGX_HOST/tnt_rest?arg1=1&argN=N
Note: since v0.1.4
The module expects JSON posted with HTTP POST, PUT (since v0.2.0), or PATCH (since v2.3.8) and carried in request body.
Server HTTP statuses:
OK - response body contains a result or an error; the error may appear only if something wrong happened within Tarantool, for instance: 'method not found'.
INTERNAL SERVER ERROR - may appear in many cases, most of them being 'out of memory' error.
NOT ALLOWED - in response to anything but a POST request.
BAD REQUEST - JSON parse error, empty request body, etc.
BAD GATEWAY - lost connection to Tarantool server(s). Since both (i.e. json -> tp and tp -> json) parsers work asynchronously, this error may appear if 'params' or 'method' does not exists in the structure of the incoming JSON, please see the protocol description for more details.
Note: this behavior will change in the future.
[ { "method": STR, "params":[arg0 ... argN], "id": UINT }, ...N ]
These all are required fields.
[ { "result": JSON_RESULT_OBJECT, "id":UINT, "error": { "message": STR, "code": INT } }, ...N ]
result - Tarantool execution result (a json object/array, etc).
Version 2.4.0+ outputs a raw result, i.e. JSON_RESULT_OBJECT.
May be null or undefined.
id - DEPRECATED in 2.4.0+ - request id. May be null or undefined.
error - a structured object which contains an internal error message. This field exists only if an internal error occurred, for instance: "too large request", "input json parse error", etc.
If this field exists, the input message was probably not passed to the Tarantool backend.
See "message"/"code" fields for details.
For instance, Tarantool has a stored procedure echo:
function echo(a, b, c, d)
return a, b, c, d
end
Syntax:
--> data sent to Server
<-- data sent to Client
rpc call 1:
--> { "method": "echo", "params": [42, 23], "id": 1 }
<-- { "id": 1, "result": [42, 23]
rpc call 2:
--> { "method": "echo", "params": [ [ {"hello": "world"} ], "!" ], "id": 2 }
<-- { "id": 2, "result": [ {"hello": "world"} ], "!" ]}
rpc call of a non-existent method:
--> { "method": "echo_2", "id": 1 }
<-- { "error": {"code": -32601, "message": "Method not found"}, "id": 1 }
rpc call with invalid JSON:
--> { "method": "echo", "params": [1, 2, 3, __wrong__ ] }
<-- { "error": { "code": -32700, "message": "Parse error" } }
rpc call Batch:
--> [
{ "method": "echo", "params": [42, 23], "id": 1 },
{ "method": "echo", "params": [ [ {"hello": "world"} ], "!" ], "id": 2 }
]
<-- [
{ "id": 1, "result": [42, 23]},
{ "id": 2, "result" : [{"hello": "world"} ], "!" ]},
]
rpc call Batch of a non-existent method:
--> [
{ "method": "echo_2", "params": [42, 23], "id": 1 },
{ "method": "echo", "params": [ [ {"hello": "world"} ], "!" ], "id": 2 }
]
<-- [
{ "error": {"code": -32601, "message": "Method not found"}, "id": 1 },
{"id": 2, "result": [ {"hello": "world"} ], "!" ]}
]
rpc call Batch with invalid JSON:
--> [
{ "method": "echo", "params": [42, 23, __wrong__], "id": 1 },
{ "method": "echo", "params": [ [ {"hello": "world"} ], "!" ], "id": 2 }
]
<-- { "error": { "code": -32700, "message": "Parse error" } }
Sometimes you have to set status or headers which came from Tarantool. For this purpose, you have to use something like ngx_lua or ngx_perl, etc.
With the methods, you can also transform the result from Tarantool into
something else.
Here is an example with ngx_lua:
-- Tarantool, stored procedure
function foo(req, ...)
local status = 200
local headers = {
["X-Tarantool"] = "FROM_TNT",
}
local body = 'It works!'
return status, headers, body
end
# Nginx, configuration
upstream tnt_upstream {
127.0.0.1:9999;
keepalive 10000;
}
location /tnt_proxy {
internal;
tnt_method foo;
tnt_buffer_size 100k;
tnt_pass_http_request on parse_args;
tnt_pass tnt_upstream;
}
location /api {
rewrite_by_lua '
local cjson = require("cjson")
local map = {
GET = ngx.HTTP_GET,
POST = ngx.HTTP_POST,
PUT = ngx.HTTP_PUT,
-- ...
}
-- hide `{"params": [...]}` from a user
ngx.req.read_body()
local body = ngx.req.get_body_data()
if body then
body = "{\\"params\\": [" .. body .. "]}"
end
local res = ngx.location.capture("/tnt_proxy", {
args = ngx.var.args,
method = map[ngx.var.request_method],
body = body
})
if res.status == ngx.HTTP_OK then
local answ = cjson.decode(res.body)
-- Read reply
local result = answ["result"]
if result ~= nil then
ngx.status = result[1]
for k, v in pairs(result[2]) do
ngx.header[k] = v
end
table.remove(result, 1)
ngx.say(cjson.encode(result))
else
ngx.status = 502
ngx.say("Tarantool does not work")
end
-- Finalize execution
ngx.exit(ngx.OK)
else
ngx.status = res.status
ngx.say(res.body)
end
';
}
syntax: tnt_pass UPSTREAM
default: no
context: location
Specify the Tarantool server backend.
upstream tnt_upstream {
127.0.0.1:9999
};
location = /tnt {
tnt_pass 127.0.0.1:9999;
}
location = /tnt_next_location {
tnt_pass tnt_upstream;
}
syntax: tnt_http_methods post, put, patch, delete, all
default: post, delete
context: location
Allow to accept one or many http methods.
If a method is allowed, the module expects JSON carried in the request
body.
If tnt_method is not set, then the name of the Tarantool stored procedure is
the protocol JSON.
Example:
location tnt {
tnt_http_methods delete;
tnt_pass 127.0.0.1:9999;
}
# Call tarantool_stored_procedure_name()
$> wget --method=delete --body-data='{"method":"lua_function", "params": [], "id": 0}' NGINX_HOST/tnt
syntax: tnt_http_rest_methods get, post, put, patch, delete, all
default: no
context: location
Allow to accept one or more REST methods.
If tnt_method is not set, then the name of the Tarantool stored procedure is
the first part of the URL path.
Example:
location tnt {
tnt_http_rest_methods get;
tnt_pass 127.0.0.1:9999;
}
# Call tarantool_stored_procedure_name()
$> wget NGINX_HOST/tarantool_stored_procedure_name/some/mega/path?q=1
syntax: tnt_pass_http_request [on|off|parse_args|unescape|pass_body|pass_headers_out|parse_urlencoded|pass_subrequest_uri]
default: off
context: location, location if
Allow to pass HTTP headers and queries to Tarantool stored procedures.
Examples #1:
location tnt {
# Also, tnt_pass_http_request can be used together with JSON communication
tnt_http_rest_methods get;
# [on|of]
tnt_pass_http_request on;
tnt_pass 127.0.0.1:9999;
}
function tarantool_stored_procedure_name(req, ...)
req.headers -- lua table
req.query -- string
return { 'OK' }
end
-- With parse_args
function tarantool_stored_procedure_name_1(req, ...)
req.headers -- lua table
req.query -- string
req.args -- query args as lua table
return { 'OK' }
end
-- With pass_body
function tarantool_stored_procedure_name_2(req, ...)
req.body -- request body, type string
end
Examples #2 (pass_headers_out):
location @tnt {
tnt_http_rest_methods get;
tnt_pass_http_request on pass_headers_out;
tnt_method tarantool_stored_procedure_name;
tnt_pass 127.0.0.1:9999;
}
location / {
add_header "X-Req-time" "$request_time";
proxy_pass http://backend;
post_action @tnt;
}
function tarantool_stored_procedure_name(req, ...)
req.headers -- lua table
req.headers['X-Req-time'] -- set by add_header
req.query -- string
return true
end
Examples #3 (parse_urlencoded):
location /tnt {
tnt_http_rest_methods post;
tnt_pass_http_request on parse_urlencoded;
tnt_method tarantool_stored_procedure_name;
tnt_pass 127.0.0.1:9999;
}
function tarantool_stored_procedure_name(req, ...)
req.headers -- a lua table
req.query -- a string
req.body[0]['q'] -- 1
req.body[1]['q'] -- 2
req.body[2]['q'] -- 3
return true
end
# Call tarantool_stored_procedure_name()
$> wget NGINX_HOST/tarantool_stored_procedure_name/some/mega/path?q=1&q=2&q=3
Examples #4 (pass_subrequest_uri):
location /web {
# Backend processing /web/foo and replying with X-Accel-Redirect to
# internal /tnt/bar
proxy_pass http://x-accel-redirect-backend;
}
location /tnt {
internal;
tnt_pass_http_request on;
tnt_method tarantool_xar_handler;
tnt_pass 127.0.0.1:9999;
}
function tarantool_xar_handler(req, ...)
print(req.uri) -- /web/foo
return true
end
location /web {
# Backend processing /web/foo and replying with X-Accel-Redirect to
# internal /tnt/bar
proxy_pass http://x-accel-redirect-backend;
}
location /tnt {
internal;
tnt_pass_http_request on pass_subrequest_uri;
tnt_method tarantool_xar_handler;
tnt_pass 127.0.0.1:9999;
}
function tarantool_xar_handler(req, ...)
print(req.uri) -- /tnt/bar
return true
end
syntax: tnt_pass_http_request_buffer_size SIZE
default: 4k, 8k
context: location
Specify the size of the buffer used for tnt_pass_http_request.
syntax: tnt_method STR
default: no
context: location, location if
Specify the Tarantool call method. It can take a nginx's variable.
Examples:
location tnt {
# Also tnt_pass_http_request can mix with JSON communication [[
tnt_http_rest_methods get;
tnt_method tarantool_stored_procedure_name;
#]]
# [on|of]
tnt_pass_http_request on;
tnt_pass 127.0.0.1:9999;
}
location ~ /api/([-_a-zA-Z0-9/]+)/ {
# Also tnt_pass_http_request can mix with JSON communication [[
tnt_http_rest_methods get;
tnt_method $1;
#]]
# [on|of]
tnt_pass_http_request on;
tnt_pass 127.0.0.1:9999;
}
function tarantool_stored_procedure_name(req, ...)
req.headers -- lua table
req.query -- string
return { 'OK' }
end
function call(req, ...)
req.headers -- lua table
req.query -- string
return req, ...
end
# OK Call tarantool_stored_procedure_name()
$> wget NGINX_HOST/tarantool_stored_procedure_name/some/mega/path?q=1
# Error Call tarantool_stored_procedure_XXX()
$> wget NGINX_HOST/tarantool_stored_procedure_XXX/some/mega/path?q=1
# OK Call api_function
$> wget NGINX_HOST/api/call/path?q=1
syntax: tnt_set_header STR STR
default: no
context: location, location if
Allows redefining or appending fields to the request header passed to the Tarantool proxied server. The value can contain text, variables, and their combinations.
Examples:
location tnt {
# Also tnt_pass_http_request can mix with JSON communication [[
tnt_http_rest_methods get;
tnt_method tarantool_stored_procedure_name;
#]]
tnt_set_header X-Host $host;
tnt_set_header X-GEO-COUNTRY $geoip_country_code;
# [on|of]
tnt_pass_http_request on;
tnt_pass 127.0.0.1:9999;
}
function tarantool_stored_procedure_name(req, ...)
req.headers['X-Host'] -- a hostname
req.headers['X-GEO-COUNTRY'] -- a geo country
return { 'OK' }
end
# OK Call tarantool_stored_procedure_name()
$> wget NGINX_HOST/tarantool_stored_procedure_name/some/mega/path?q=1
syntax: tnt_send_timeout TIME
default: 60s
context: http, server, location
The timeout for sending TCP requests to the Tarantool server, in seconds by default.
It's wise to always explicitly specify the time unit to avoid confusion.
Time units supported are:
s(seconds), ms(milliseconds), y(years), M(months), w(weeks),
d(days), h(hours), and m(minutes).
syntax: tnt_read_timeout TIME
default: 60s
context: http, server, location
The timeout for reading TCP responses from the Tarantool server, in seconds by default.
It's wise to always explicitly specify the time unit to avoid confusion.
Time units supported are: s(seconds), ms(milliseconds), y(years),
M(months), w(weeks), d(days), h(hours), and m(minutes).
syntax: tnt_connect_timeout TIME
default: 60s
context: http, server, location
The timeout for connecting to the Tarantool server, in seconds by default.
It's wise to always explicitly specify the time unit to avoid confusion.
Time units supported are: s(seconds), ms(milliseconds), y(years),
M(months), w(weeks), d(days), h(hours), and m(minutes).
This time must be strictly less than 597 hours.
syntax: tnt_buffer_size SIZE
default: 4k, 8k
context: http, server, location
This buffer size is used for reading Tarantool replies, but it's not required to be as big as the largest possible Tarantool reply.
syntax: tnt_next_upstream [ error | timeout | invalid_response | off ]
default: error timeout
context: http, server, location
Specify which failure conditions should cause the request to be forwarded to another upstream server. Applies only when the value in tnt_pass is an upstream with two or more servers.
syntax: tnt_next_upstream_tries SIZE
default: 0
context: http, server, location
Limit the number of possible tries for passing a request to the next server. The 0 value turns off this limitation.
syntax: tnt_next_upstream_timeout TIME
default: 0
context: http, server, location
Limit the time during which a request can be passed to the next server. The 0 value turns off this limitation.
syntax: tnt_pure_result [on|off]
default: off
context: http, server, location
Whether to wrap Tarantool response or not.
When this option is off:
{"id":0, "result": [ 1 ]}
When this option is on:
[[1]]
DEPRECATED in 2.4.0+, RETURNED IN 2.5.0-rc2+
syntax: tnt_multireturn_skip_count [0|1|2]
default: 0
context: http, server, location
Note: Use this option wisely, it does not validate the outgoing JSON! For details you can check this issue: https://github.com/tarantool/nginx_upstream_module/issues/102
The module will skip one or more multireturn parts when this option is > 0.
When it is set to 0:
{"id":0, "result": [[1]]}
When it is set to 1:
{"id":0, "result": [1]}
When it is set to 2:
{"id": 0, "result": 1}
syntax: tnt_{OPT} [ARGS] [FMT]
Tarantool stores data in tuples. A tuple is a list of elements. Each element is a value or an object, and each element should have a strong type. The tuple format is called MsgPack, it's like JSON in a binary format.
The main goal of Format (see [FMT] above) is to enable conversion between a query string and MsgPack without losing type information or value.
The syntax is: {QUERY_ARG_NAME}=%{FMT_TYPE}
Please look carefully for yours url encoding!
A good example is (also see examples tnt_update and tnt_upsert):
HTTP GET ... /url?space_id=512&value=some+string
it could be matched by using the following format 'space_id=%%space_id,value=%s'
Also this works with HTTP forms, i.e. HTTP POST, HTTP PUT and so on.
Here is a full list of {FMT_TYPE} types:
TUPLES
%n - int64
%f - float
%d - double
%s - string
%b - boolean
Special types
%%space_id - space_id
%%idx_id - index_id
%%off - [select](#tnt_select) offset
%%lim - [select](#tnt_select) limit
%%it - [select](#tnt_select) iterator type, allowed values are:
eq,req,all,lt,le,ge,gt,all_set,any_set,
all_non_set,overlaps,neighbor
KEYS (for [tnt_update](#tnt_update))
%kn - int64
%kf - float
%kd - double
%ks - string
%kb - boolean
Operations (for [tnt_upsert](#tnt_upsert))
%on - int64
%of - float
%od - double
%os - string
%ob - boolean
Examples can be found at:
examples/simple_rest_client.pyexamples/simple_rest_client.shsyntax: tnt_insert [SIZE or off] [FMT]
default: None
context: location, location if
HTTP methods GET, POST, PUT, PATCH, DELETE
Content-Typer default, application/x-www-form-urlencoded
This directive allows executing an insert query with Tarantool.
Returns HTTP code 4XX if client's request doesn't well formatted. It means, that this error raised if some of values missed or has wrong type.
Returns HTTP co
Content type
Image
Digest
Size
98.2 MB
Last updated
about 8 years ago
docker pull tarantool/tarantool-nginx