http - a Tarantool rock for an HTTP client and a server
- Tarantool 1.6.5+ with header files (tarantool && tarantool-dev packages)
Clone repository and then build it using CMake:
git clone https://github.com/bsnote/tarantool-http.git
cd tarantool-http && cmake . -DCMAKE_BUILD_TYPE=RelWithDebugInfo
make
make install
You can also use LuaRocks:
luarocks install https://raw.githubusercontent.com/bsnote/tarantool-http/master/http-scm-1.rockspec --local
See [tarantool/rocks][TarantoolRocks] for LuaRocks configuration details.
client = require('http.client')
print(client.get("http://mail.ru/").status)
Any kind of HTTP 1.1 query (no SSL support yet).
Issue an HTTP request at the given URL (url
).
method
can be either GET
or POST
.
If body
is nil
, the body is an empty string, otherwise
the string passed in body
.
opts
is an optional Lua table with methods, and may contain the following
keys:
headers
- additional HTTP headers to send to the server
Returns a Lua table with:
status
- HTTP response statusreason
- HTTP response status textheaders
- a Lua table with normalized HTTP headersbody
- response bodyproto
- protocol version
r = require('http.client').request('GET', 'http://google.com')
r = require('http.client').request('POST', 'http://google.com', {}, 'text=123')
The server is an object which is configured with HTTP request handlers, routes (paths), templates, and a port to bind to. Unless Tarantool is running under a superuser, ports numbers below 1024 may be unavailable.
The server can be started and stopped any time. Multiple servers can be creatd.
To start a server:
- create it:
httpd = require('http.server').new(...)
- configure "routing"
httpd:route(...)
- start it with
httpd:start()
- stop with
httpd:stop()
httpd = require('http.server').new(host, port[, { options } ])
'host' and 'port' must contain the interface and port to bind to. 'options' may contain:
max_header_size
(default is 4096 bytes) - a limit on HTTP request header sizeheader_timeout
(default: 100 seconds) - a time out until the server stops reading HTTP headers sent by the client. The server closes the client connection if the client can't manage to send its headers in the given amount of time.app_dir
(default: '.', the server working directory) - a path to the directory with HTML templates and controllershandler
- a Lua function to handle HTTP requests (this is a handler to use if the module "routing" functionality is not needed).charset
- the character set for server responses of typetext/html
,text/plain
andapplication/json
.display_errors
- return application errors and backraces to client (like PHP)log_errors
- log application errors usinglog.error()
It is possible to automatically route requests between different handlers, depending on request path. The routing API is inspired by Mojolicious API.
Routes can be defined using either:
- and exact match, e.g. "index.php"
- with simple regular expressions
- with extended regular expressions
Route examples:
'/' -- a simple route
'/abc' -- a simple route
'/abc/:cde' -- a route using a simple regular expression
'/abc/:cde/:def' -- a route using a simple regular expression
'/ghi*path' -- a route using an extended regular expression
To conigure a route, use 'route()' method of httpd object:
httpd:route({ path = '/path/to' }, 'controller#action')
httpd:route({ path = '/', template = 'Hello <%= var %>' }, handle1)
httpd:route({ path = '/:abc/cde', file = 'users.html.el' }, handle2)
...
route()
first argument is a Lua table with one or several keys:
file
- a template file name (if relative, then to path{app_dir}/tempalates
, where app_dir is the path set when creating the server). If no template file name extention is provided, the extention is set to ".html.el", meaning HTML with embedded Luatemplate
- template Lua variable name, in case the template is a Lua variable. Iftemplate
is a function, it's called on every request to get template body. This is useful if template body must be taken from a databasepath
- route path, as described earliername
- route name
The second argument is the route handler to be used to produce a response to the request.
A typical usage is to avoid passing file
and template
arguments,
since these take time to evaluate, but these arguments are useful
for writing tests or defining HTTP servers with just one "route".
The handler can also be passed as a string of form 'filename#functionname'.
In that case, handler body is taken from a file in {app_dir}/controllers
directory.
public
- is a path to store static content. Anything in this path defines a route which matches the file name, and the server serves this file automatically, as is. Note, that the server doesn't use sendfile(), and reads the entire content of the file in memory before passing it to the client. Caching is used, unless is turned on. So this is suitable for large files, use nginx instad.templates
- a path to templatescontrollers
- a path to Lua controllers lua. For example, controller name 'module.submodule#foo' maps to{app_dir}/controllers/module.submodule.lua
.
A route handler is a function which accept one argument - Request and returns one value - Response.
function my_handler(req)
-- req is a Request object
local resp = req:render({text = req.method..' '..req.path })
-- resp is a Response object
resp.headers['x-test-header'] = 'test';
resp.status = 201
return resp
end
req.method
- HTTP request type (GET
,POST
etc)req.path
- request pathreq.query
- request argumentsreq.proto
- HTTP version (for example,{ 1, 1 }
isHTTP/1.1
)req.headers
- normalized request headers. A normalized header is in lower case, all headers joined together into a single string.req.peer
- a Lua table with information about remote peer (likesocket:peer()
)tostring(req)
- returns a string representation of the requestreq:request_line()
- returns request bodyreq:read(delimiter|chunk|{delimiter = x, chunk = x}, timeout)
- read raw request body as stream (see socket:read())req:json()
- returns lua table from json JSON requestreq:post_param(name)
- returns a single POST request parameter value. Ifname
isnil
, returns all parameters as a Lua table.req:query_param(name)
- returns a single GET request parameter value. If name isnil
, returns a Lua table withall argumentsreq:param(name)
- any request parameter, either GET or POSTreq:cookie(name)
- to get a cookie in the requestreq:stash(name[, value])
- get or set a variable "stashed" when dispatching a routereq:url_for(name, args, query)
- returns the route exact URLreq:render({})
- create Response object with rendered templatereq:redirect_to
- create Response object with HTTP redirect
resp.status
- HTTP response coderesp.headers
- a Lua table with normalized headersresp.body
- response body (string|table|wrapped_iterator)resp:setcookie({ name = 'name', value = 'value', path = '/', expires = '+1y', domain = 'example.com'))
- addsSet-Cookie
headers to resp.headers
function my_handler(req)
return {
status = 200,
headers = { ['content-type'] = 'text/html; charset=utf8' },
body = [[
<html>
<body>Hello, world!</body>
</html>
]]
}
end
function hello(self)
local id = self:stash('id') -- here is :id value
local user = box.space.users:select(id)
if user == nil then
return self:redirect_to('/users_not_found')
end
return self:render({ user = user })
end
httpd = box.httpd.new('127.0.0.1', 8080)
httpd:route(
{ path = '/:id/view', template = 'Hello, <%= user.name %>' }, hello)
httpd:start()
controller
- controller nameaction
- handler name in the controllerformat
- the current output format (e.g.html
,txt
). Is detected automatically based on requestpath
(for example,/abc.js
- setsformat
tojs
). When producing a response,format
is used to sservet response 'Content-type:'.
Do get a cookie, use:
function show_user(self)
local uid = self:cookie('id')
if uid ~= nil and string.match(uid, '^%d$') ~= nil then
local user = box.select(users, 0, uid)
return self:render({ user = user })
end
return self:redirect_to('/login')
end
To set a cookie, use cookie()
method as well, but pass in a Lua
table defining the cookie to be set:
function user_login(self)
local login = self:param('login')
local password = self:param('password')
local user = box.select(users, 1, login, password)
if user ~= nil then
return self:redirect_to('/'):
set_cookie({ name = 'uid', value = user[0], expires = '+1y' })
end
-- do login again and again and again
return self:redirect_to('/login')
end
The table must contain the following fields:
-
name
-
value
-
path
(optional, if not set, the current request path is used) -
domain
(optional) -
expires
- cookie expire date, or expire offset, for example: -
1d
- 1 day -
+1d
- the same -
23d
- 23 days -
+1m
- 1 month (30 days) -
+1y
- 1 year (365 days)
Lua can be used inside a response template, for example:
<html>
<head>
<title><%= title %></title>
</head>
<body>
<ul>
% for i = 1, 10 do
<li><%= item[i].key %>: <%= item[i].value %></li>
% end
</ul>
</body>
</html>
To embed Lua into a template, use:
<% lua-here %>
- insert any Lua code, including multi-line. Can be used in any location in the template.% lua-here
- a single line Lua substitution. Can only be present in the beginning of a line (with optional preceding spaces and tabs, which are ignored).
A few control characters may follow %
:
=
(e.g.,<%= value + 1 %>
) - runs the embedded Lua and inserts the result into HTML. HTML special characters, such as<
,>
,&
,"
are escaped.==
(e.g.,<%== value + 10 %>
) - the same, but with no escaping.
A Lua statement inside the template has access to the following environment:
- the Lua variables defined in the template
- the stashed variables
- the variables standing for keys in the
render
table
Helpers are special functions for use in HTML templates, available in all templates. They must be defined when creating an httpd object.
Setting or deleting a helper:
-- setting a helper
httpd:helper('time', function(self, ...) return box.time() end)
-- deleting a helper
httpd:helper('some_name', nil)
Using a helper inside an HTML template:
<div>
Current timestamp: <%= time() %>
</div>
A helper function can receive arguments. The first argument is always the current controller. The rest is whatever is passed to the helper from the template.
It is possible to define additional functions invoked at various stages of request processing.
If handler
is given in httpd options, it gets
involved on every HTTP request, and the built-in routing
mechanism is unused (no other hooks are called in this case).
Is invoked before a request is routed to a handler. The first argument of the hook is the HTTP request to be handled. The return value of the hook is ignored.
This hook could be used to log a request, or modify request headers.
Is invoked after a handler for a route is executed.
The argument of the hook is the request, passed into the handler, and the response produced by the handler.
This hook can be used to modify the response. The return value of the hook is ignored.
For additional examples, see documentation and tests.