Public | Automated Build

Last pushed: a month ago
Short Description
lucid builds
Full Description

Lua Web API Toolkit


A web API toolkit playground for the Lua programming
language.

Table of Contents

<!-- TOC depthFrom:1 depthTo:2 withLinks:1 updateOnSave:1 orderedList:0 -->

<!-- /TOC -->

Installation

luarocks install --server=http://luarocks.org/dev lucid

... or use docker images.

Overview

Using a function:

local http = require 'http'

local app = http.app.new()
app:use(http.middleware.routing)

app:get('', function(w, req)
    return w:write('Hello World!\n')
end)

return app()

... or a metaclass:

local class = require 'core.class'
local web = require 'web'

local WelcomeHandler = class {
    get = function(self)
        self.w:write('Hello World!\n')
    end
}

local all_urls = {
    {'', WelcomeHandler}
}

local options = {
    urls = all_urls
}

return web.app({web.middleware.routing}, options)

see more here.

Setup

Install development dependencies:

sudo make debian
make env nginx
make test qa
eval "$(env/bin/luarocks path --bin)"

alternative environments:

make env LUA_VERSION=5.2.4
make env LUA_IMPL=luajit LUA_VERSION=2.0.5
make env LUA_IMPL=luajit LUA_VERSION=2.1.0-beta2

Run

Check from the command line:

lurl -v demos/http/hello.lua /

Serve files with a web server:

export app=demos.http.hello ; make run
curl -v http://localhost:8080

Use docker:

docker run -it --rm -p 8080:8080 -v `pwd`/demos:/app \
    -e app=http.hello akorn/lucid:dev-luajit2.1-alpine

Open your browser at http://localhost:8080

HTTP API Reference

Application

http.app.new([options])

The app object by convention corresponds to HTTP application. Create it by
calling http.app.new function exported by the http module:

local http = require 'http'
local app = http.app.new()

It also accepts optional table options that affects how the application
behaves:

local app = http.app.new {
    root_path = '/api/v1/'
}

Later options can be accessed as app.options.

The same options are shared as a parameter to
middleware initialization and
available in HTTP request object as req.options.

The app object has methods for configuring middleware:

app:use(http.middleware.routing)

and routing HTTP requests:

app:get('', function(w, req)
    return w:write('Hello World!\n')
end)

Here app:get function corresponds to HTTP GET verb, app:post to HTTP
POST, etc.

Finally you call app to build url mapping, chain middlewares and run it
through initialization step.

return app()

This call returns the first middleware registered with app:use.

Properties

http.app.http_verbs

The table of adapted HTTP verbs can be accessed as
http.app.http_verbs,
which is an association between a function name and HTTP verb, e.g.
post = POST, etc. The association happens during application initialization
only, thus does not affect runtime.

HTTP verb OPTIONS is not in http.app.http_verbs.

Use HTTP verb in upper case if is not in http.app.http_verbs.

app:OPTIONS('', function(w, req)
end)

app.options

The app.options table has properties that are specific for the application.

app.options.root_path
-- '/'

These options remain throughout the life of the application. You can access
options during middleware initialization and in HTTP request object as
req.options.

The app object is not supposed to be shared with request
handlers. Use req.options instead.

The following table describes the properties of the options object.

Property Description Default
urls Keeps url path mapping to request handler {}

Events

app.on('mounted', function(parent))

The mounted event is fired on a sub-app, when it is mounted on a
parent app. The parent app is passed to the function.

Sub-app will:

  • Not inherit the value of options of the parent application.
  • Keep own options unchanged.
  • Use any values from the parent application as necessary.

The following example shows the use of mounted event.

local http = require 'http'

local greetings = http.app.new()
greetings:on('mounted', function(parent)
    print('mounted')
end)
greetings:get('hi', function(w)
    return w:write('hi')
end)

local app = http.app.new()
app:use(http.middleware.routing)
app:add('greetings/', greetings)
return app()

Methods

app:add(pattern, sub_app)

Mounts specified sub_app at the pattern: the application handles all
requests that match url path according to pattern.

This allows you to build modular or composable applications.

Example: composable application

Here is child application that we could reuse later.

-- child.lua
local http = require 'http'

local app = http.app.new()
app:get('hi', function(w, req)
    return w:write('Hello World!\n')
end)

return app

The child app object is returned without a call for initialization.

The parent app builds url mapping for sub_app app.

The middlewares registered in sub_app are ignored.

The route that matches any path that follows its path immediately after
"/greetings/" will be handed to child application.

-- main.lua
local http = require 'http'

local app = http.app.new()
app:add('greetings/', require 'child')
app:use(http.middleware.routing)

return app()

The child application handles requests to /greetings/hi.

The main application can extend child application routing as necessary.

app:get('greetings/hallo', function(w, req)
    return w:write('Hallo Welt!\n')
end)

By default the main application can not override patterns (the routing
middleware treats this as an error, unless allow_path_override option).

local app = http.app.new {
    allow_path_override = true
}
app:add('greetings/', require 'child')
app:get('greetings/hi', function(w, req)
    return w:write('hey!\n')
end)

app:all(pattern [, route_name] [, function(following, options), ...], function(w, req))

Routes an HTTP request regardless HTTP verb.

app:all('hi', function(w)
    return w:write('hi')
end)

The actual HTTP verb can be obtained from request, e.g.:

app:all('hi', function(w, req)
    return w:write(req.method)
end)

The following table describes the arguments.

Argument Description
pattern The path for which the middleware function is invoked, it can be a string representing a path or a regular expression pattern.
route_name The name used to address this route in reverse URL lookup. Optional.
function (following, options) Middleware function. See below.
function (w, req) Request handler function. See below.
function(following, options)

Middleware function (interceptor) can be used to impose pre-conditions on a
route handler.

local function middleware(following, options)
    return function(w, req)
        return following(w, req)
    end
end

The following object by convention corresponds to the next route handler,
which is a function(w, req); options is a table used to initialize
application and holds properties that are specific to application and shared
across.

The middleware function is called only once during application initialization,
while returning function for route handling on each request routed.

The middleware function does not influence routing, that means if a call to
following has not been made, the processing is still considered successful,
an attempt to find a next matching route is not performed.

A return value of middleware function is ignored, however
return following(w, req) enables Lua's tail call, thus generally preferred.

Example: middleware function

Here is an example that shows the use of multiple middleware functions:

local function interceptor1(following, options)
  return function(w, req)
    print('before1')
    following(w, req)
    print('after1')
  end
end

local function interceptor2(following, options)
  return function(w, req)
    print('before2')
    following(w, req)
    print('after2')
  end
end

app:all('hi', interceptor1, interceptor2, function(w, req)
  print('hi')
  return w:write(req.method)
end)

The interceptor1 and interceptor2 middleware functions intercept all
calls (one after another) to corresponding route handler function.

You can use multiple middleware functions, the execution order is from left to
right. The above example prints:

before1
before2
hi
after2
after1
function(w, req)

HTTP route / request handler function processes application logic and writes
response if any.

local function handler(w, req)
    return w:write('hi')
end

The w object by convention corresponds to HTTP response
writer, req to HTTP request. If req object is not used it can be
safely omitted.

local function handler(w)
    return w:write('hi')
end

A return value of HTTP route handler function is ignored, however
return following(w, req) enables Lua's tail call, thus generally
preferred.

app:route(pattern [, route_name])

Returns an instance of a route, which can be used to further handle HTTP verbs.

app:route('hi')
:get(function(w, req)
    -- respond to HTTP GET request
end)
:post(function(w)
    -- respond to HTTP POST request
end)

Use app:route() to specify HTTP verb that does not have a valid map in
http.app.http_verbs.

app:route('hi')['OPTIONS'](function(w)
end)

Use app:route() to avoid duplicate routing patterns and potential typo
errors.

app:use(middleware)

Mounts the specified middleware function. The
middleware function is executed for each request served by the application.

local http = require 'http'

local app = http.app.new()
app:use(http.middleware.routing)

Request

The req object represents the HTTP request and has properties for the
request method, path, HTTP headers, and so on. By convention, the object is
always referred to as req.

Properties

req.options

This property holds a reference to the instance of the application
app.options.

req.method

Contains a string corresponding to the HTTP method of the request: GET, POST,
PATCH, and so on.

req.path

Contains the path part of the request URL.

-- http://blog.example.com/api/v1/posts?q=lua
req.path
-- "/api/v1/posts"

req.route_args

This property is a table containing properties mapped to the named route
“arguments” set by routing middleware.

app:get('{locale}/user/{user_id:i}', function(w, req)
    return w:write(req:route_args.user_id)
end)
-- path: /en/user/123
req.route_args
-- {["locale"] = "en", ["user_id"] = "123"}

The req.route_args can have a reserved property route_name
if there is an associated name with the request handler.

app:get('{locale}/user/{user_id:i}', 'user', function(w, req)
end)
-- path: /en/user/123
req.route_args.route_name
-- "user"

If there is no route arguments, it is the empty table, {}.

req.query

This property is a table containing a property for each query string parameter
(case-sensitive) in the route.

The value is a string for a single occurrence of query parameter or a
table for multiple values.

-- http://blog.example.com/api/v1/posts?q=lua&page=2
req.query
-- {["q"] = "lua", ["page" = "2"]}
req.query.q
-- "lua"
req.query.page
-- "2"

Use req.query or req:parse_query().

app:get('', function(w, req)
    local qs = req.query or req:parse_query()
    -- ...
end)

If there is no query string, it is the empty table, {}.

-- http://blog.example.com/api/v1/posts
req.query
-- {}

The req.query table is nil unless you call req:parse_query()
first.

req.headers

This property is a table containing a property for each HTTP header name
(lowercase).

The value is a string for a single occurrence of HTTP header or a
table for multiple values.

Use req.headers or req:parse_headers().

app:get('', function(w, req)
    local headers = req.headers or req:parse_headers()
    -- ...
end)

The req.headers table is nil unless you call
req:parse_headers() first.

Use req.headers to determine whenever the request is AJAX.

local headers = self.headers or self:parse_headers()
local is_ajax = headers['x-requested-with'] == 'XMLHttpRequest'

req.cookies

This property is a table that contains HTTP cookies (case-sensitive).

Use req.cookies or req:parse_cookie().

app:get('', function(w, req)
    local cookies = req.cookies or req:parse_cookie()
    -- ...
end)

The req:parse_cookie() uses an empty name if not specified.

-- Cookie:
req.cookies
-- {['']=''}
-- Cookie: abc
req.cookies
-- {['']='abc'}

Returns a table with mapped cookie name to value.

-- Cookie: a=1
req.cookies
-- {['a']='1'}
-- Cookie: a=1; b=2
req.cookies
-- {a='1', b='2'}

Supports cookie value with spaces.

-- Cookie: c1=a b; c2= ; c3=a ; c4= b
req.cookies
-- {['c1']='a b', ['c2']=' ', ['c3']='a ', ['c4']=' b'}

If there is no cookies, it is the empty table, {}.

The req.cookies table is nil unless you call
req:parse_cookies() first.

req.body

This property type depends on MIME type of incoming HTTP request.

MIME Type Value
application/x-www-form-urlencoded a table that contains all the current request POST query arguments.
application/json a table that corresponds to parsed JSON object, either array or map.
multipart/form-data a string, in-memory request body data.

Use req.body or req:parse_body().

local values = req.body or req:parse_body()

To force in-memory request bodies, set
client_body_buffer_size
to the same size value in
client_max_body_size.

This function returns nil if the request body has zero size.

Methods

req:parse_query()

Parses HTTP query string.

See req.query.

req:parse_headers()

Parses HTTP headers.

See req.headers.

req:parse_cookie()

Parses HTTP cookie header.

See req.cookies.

req:parse_body()

Parses HTTP body.

See req.body.

req:server_parts()

Returns multiple values representing various server parts.

-- http://blog.example.com/api/v1/posts?q=lua
local scheme, host, port = req:server_parts()
-- "http", "blog.example.com", "80"

Use req.server_parts() to build request URL authority part.

local authority = scheme .. '://' .. host
    .. (port == '80' and '' or ':' .. port)
-- http://blog.example.com

Response Writer

The w object represents the HTTP response and has properties for the
response HTTP headers. By convention, the object is always referred to as w.

Properties

w.headers

This property is a table that contains HTTP headers. The keys of the returned
table are the header names (case-insensitive) and the values are the respective
header values.

The value is a string for a single occurrence of HTTP header or a
table for multiple values.

-- w.headers['X-Request-Count'] = '100'
w.headers['X-Request-Count']
-- "100"
w.headers['x-request-count']
-- "100"

Use table to set multiple values.

-- w.headers['Set-Cookie'] = {'a=1', 'b=2'}
w.headers['set-cookie']
-- {"a=1", "b=2"}

Use value nil to remove corresponding HTTP response header.

Methods

w:get_status_code()

Returns the status code which was sent to the client.

nil value indicates successful HTTP response.

w:set_status_code(code)

Controls the HTTP status code that will be sent to the client when the headers
get flushed.

w:set_status_code(403)

w:write(c)

Sends a chunk c of the response body. This method may be called multiple
times to provide successive parts of the body.

The response body is omitted when the request is a HEAD request. The 204
and 304 responses must not include a body.

The behavior of this method depends on adapter in use.

Adapter Behavior
buffered The w:write() calls are buffered. The headers and body is sent to the client when application will finish processing request.
stream The first time w:write() is called, it will send the headers and the first chunk of the body to the client.

This method sends the raw HTTP body and do not perform any body
encodings.

w:flush()

Flushes response output to the client asynchronously.

This method returns immediately without waiting for output data to be
written into the system send buffer.

This function has no effect in case of buffered adapter in use.

w:add_header(name, value)

Adds a single string value for HTTP header.

w:addHeader('Set-Cookie', 'c=100')

If this header already exists it will add value so multiple headers withthe same
name will be sent.

w:set_cookie(s)

Sets specified cookie string by adding Set-Cookie HTTP header.

w:set_cookie(http.cookie.dump {
    name = 'c', value = '100', http_only = true
})
w.headers['Set-Cookie']
-- c=100; HttpOnly

Use this method to delete cookie.

w:set_cookie(http.cookie.delete {name = 'c'})
w.headers['Set-Cookie']
-- c=; Expires=Thu, 01 Jan 1970 00:00:00 GMT

w:redirect(absolute_url[, status_code=302])

Redirects to the absolute URL with specified status that corresponds to an HTTP
status code. If not specified, status defaults to “302 “Found”.

w:redirect('http://example.com')

Use this method together with routing mixin to redirect to named routes.

local mixin = require 'core.mixin'
local http = require 'http'

mixin(http.Request, http.mixins.routing)

app:get('', function(w, req)
    return w:redirect(req:absolute_url_for('welcome'))
end)

app:get('welcome', 'welcome', function(w, req)
    return w:write('Hello World!\n')
end)

Cookie

An HTTP cookie (browser cookie) is a small piece of data that a server sends to
the user's web browser. The browser may store it and send it back with the next
request to the same server.

The http.cookie module represents the HTTP cookie.

http.cookie.dump(options)

Returns a string that represents the HTTP cookie per options provided.

http.cookie.dump {name='a', value='1'}
-- "a=1"
http.cookie.dump {name='a', value='1', path='/abc/'}
-- "a=1; Path=/abc/"
http.cookie.dump {name='a', value='1', domain='example.com'}
-- "a=1; Domain=example.com"
http.cookie.dump {name='a', value='1', expires=1423473707}
-- "a=1; Expires=Mon, 09 Feb 2015 09:21:47 GMT"
http.cookie.dump {name='a', value='1', max_age=600}
-- "a=1; Max-Age=600"
http.cookie.dump {name='a', value='1', same_site='Strict'}
-- "a=1; SameSite=Strict"
http.cookie.dump {name='a', value='1', http_only=true}
-- "a=1; HttpOnly"
http.cookie.dump {name='a', value='1', secure=true}
-- "a=1; Secure"

The following table describes the options.

Property Type Description
name string Name of the cookie.
value string Cookie value.
path string Path for the cookie.
domain string Domain name for the cookie.
expires number Expiry date of the cookie in GMT. If not specified creates a session cookie.
max_age number The expiry time relative to the current time in milliseconds.
same_site string Can be used to disable third-party usage for a specific cookie. Either Lax or Strict.
http_only boolean Flags the cookie to be accessible only by the web server.
secure boolean Marks the cookie to be used with HTTPS only.

http.cookie.delete(options)

Returns a string that corresponds to an empty expired cookie.

http.cookie.delete {name='a'}
-- a=; Expires=Thu, 01 Jan 1970 00:00:00 GMT
http.cookie.delete {name='a', path='/abc/'}
-- a=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/abc/

See options in http.cookie.dump.

Middlewares

Middleware functions are functions that have
access to the
response writer object (w),
the request object (req), the
following function, and the application
options in the application’s request processing lifecycle.

The following function is a function, when invoked, executes the
middleware succeeding the current one.

If the current middleware function does not end the request processing cycle,
it must call following(w, req) to pass control to the next middleware
function.

Configuration

Middleware function receives application options during
initialization. However sometimes it is useful to override some settings per
middleware without affecting application options.

Use http.middleware.opt to override any middleware specific options over
those in application options.

local http = require 'http'
local opt = require 'http.middleware.opt'

local app = http.app.new {
    option1 = 'A'
}

local function my_middleware(following, options)
    local option1 = options.option1
    -- option1 == 'B'
    return function(w, req)
        return following(w, req)
    end
end

app:use(opt(my_middleware, {
    option1 = 'B' -- this will override default in app.options
}))

app:use(http.middleware.routing)
app:get('', function(w, req)
    -- req.options.option1 == 'A'
end)
return app()

authcookie

Authentication cookie middleware implements creation of signed and encrypted
authentication session cookie.

Use response writer w.principal property to set or delete the security
principal associated with request authentication.

local http = require 'http'
local authcookie = http.middleware.authcookie

app:get('signin', authcookie, function(w, req)
    w.principal = {id = 'john.smith', roles = {admin = true}}
end)

app:get('signout', authcookie, function(w, req)
    w.principal = nil
end)

The authentication cookie is set only on successful status code (2XX).

Use app.options to configure middleware.

local ticket = require 'security.crypto.ticket'
local digest = require 'security.crypto.digest'
local cipher = require 'security.crypto.cipher'
local http = require 'http'

local app = http.app.new {
    ticket = ticket.new {
        -- digest = digest.new('sha256'),
        digest = digest.hmac('ripemd160', 'b`*>Z!P4pf99%p,)'),
        cipher = cipher.new {
            cipher = 'aes128',
            key = 'DK((-x=e[.2cLq]f',
            iv = 'b#KXN>H9"j><f2N`'
        }
    },
    auth_cookie = {
        name = '_a'
    },
    principal = require 'security.principal'
}

The following table describes the configuration options.

Property Type Description
ticket table Required. An object that provides secure string encoding, ticket:encode(s).
auth_cookie table Optional. Defaults to {name='_a'}. The http_only is always true. If path is not specified, fallbacks to options.root_path or /. See more here.
principal table Optional. An object that provides function principal.dump(p), where p is a table like: {id='', roles={role1=true}, alias='', extra=''}. Defaults to require 'security.principal'.

Use auth_cookie.max_age to control timeout before authentication cookie
expires.

authorize

Authorization middleware implements verification of signed and encrypted
authentication session cookie.

Use request req.principal property to get the security principal associated
during request authentication.

app:get('secure', authorize, function(w, req)
    if not req.principal.roles['admin'] then
        return w:set_status_code(403)
    end
    -- req.principal.id
end)

The middleware responds with HTTP status code 401 (Unauthorized) in case the
authentication cookie is not in the request or cannot be decoded.

The authentication cookie lifetime is automatically extended once cookie time
left is less than a half of cookie max age.

See configuration options in authcookie middleware.

caching

TODO

cors

This middleware implements cross-origin resource sharing.

local http = require 'http'

app:use(http.middleware.cors)

Use app.options to configure middleware.

local app = http.app.new {
    cors = http.cors.new {
        allow_credentials = true,    
        allowed_origins = {'*'},
        allowed_methods = {'GET', 'HEAD', 'POST', 'PUT', 'DELETE'},
        allowed_headers = {'content-type', 'x-requested-with'},
        exposed_headers = {'content-length', 'etag'},
        max_age = 180
    }
}

The following table describes the configuration options.

Property Type Description
allow_credentials boolean Indicates whether the client is allowed to send credentials (cookies, authorization headers, etc) to the server. Defaults to false.
allowed_origins table Required. Indicates whether the response can be shared with resources with the given origin. For requests without credentials, specify "*" as a wildcard, thereby allowing any origin to access the resource. Otherwise, specify a URI that may access the resource.
allowed_methods table Specifies the methods allowed when accessing the resource in response to a preflight request. Defaults to {'GET', 'HEAD'}.
allowed_headers table Used in response to a preflight request to indicate which HTTP headers will be available via Access-Control-Expose-Headers when making the actual request.
exposed_headers table Indicates which headers can be exposed as part of the response. Only simple response headers are exposed. Use this property to allow clients to access other headers.
max_age number How long the results of a preflight request (that is the information contained in the Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) can be cached.

routing

Routing refers to the definition of endpoints (URI paths) and how they respond to
client requests.

local http = require 'http'

app:use(http.middleware.routing)

A route method is derived from one of the HTTP methods and is attached to the
app object. Routing supports the following methods that correspond to HTTP
verbs: get, head, post, patch, put, delete.

Route path, in combination with a request method, define the endpoint at which
requests can be dispatched. Route path can be string pattern.

app:post('signin', function(w, req)
end)

There is a special routing method, app:all(), which is used to respond to all
request methods.

app:all('', function(w, req)
end)

Route arguments are named URL path segments that used to capture the values
specified at their position in the URL path. The captured values are populated in
the req.route_args table, with the name of the route arguments specified in
the URL path as the respective keys in the table.

-- URL: /en/user/123
-- route path: {locale}/user/{user_id:i}
-- req.route_args: {['locale'] = "en", ['user_id'] = "123"}

Use route arguments in the path.

app:get('{locale}/user/{user_id:i}', function(w, req)
end)

Because routing supports string patterns, specify type of the matching path
segment.

Name Pattern Description
i, int, number, digits %d+ One or more digits.
w, word %w+ One or more word characters.
s, segment, part [^/]+ Everything until /.
*, a, any, rest .+ Any match.

Use named route to build URL from name.

-- URL: /en/user/123
-- route path: {locale}/user/{user_id:i}
req:path_for('user', {locale='de', user_id='1'})
-- "/de/user/1"
req:path_for('user', {user_id='1'})
-- "/en/user/2"
req:path_for('user')
-- "/en/user/123"
req:absolute_url_for('user')
-- "http://localhost:8080/en/user/123"

If route argument is not specified, it is inherited from req.route_args.

Use routing mixin to build URL from name.

local mixin = require 'core.mixin'
local http = require 'http'

mixin(http.Request, http.mixins.routing)

app:get('{locale}/user/{user_id:i}', 'user', function(w, req)
    -- req:path_for('user', {user_id='1'})
    -- req:absolute_url_for('user')
end)

Use app:route to chain multiple request handlers to the same URL path.

app:route('users')
:get(function(w, req)
end)
:post(function(w, req)
end)

The following table describes the configuration options.

Property Type Description
root_path string The URL path on which a router instance to be mounted. Defaults to /.
router object Defaults to require 'routing.router'.
urls table Holds all URL mapping for the application.
allow_path_override boolean Specifies whenever allowed to override path that is already defined. Defaults to false.

The routing middleware responds with HTTP status 404 (Not Found) in case there
is not match for req.path.

Mixins

A mixin is a special kind of multiple inheritance. Specifically, it is used to provide
optional features or reuse of particular feature in different classes.

The mixins are not made to stand on their own. Usually, mixins assume some
context which they extend.

local mixin = require 'core.mixin'

json mixin

Extends response writer (w) with ability to send JSON.

Use mixin to extend response writer:

local mixin = require 'core.mixin'
local http = require 'http'
mixin(http.ResponseWriter, http.mixins.json)

w:json(obj)

Sends a JSON response. This method sends a response (with the content-type
application/json) that is the obj parameter converted to a JSON string
using core.encoding module.

app:get('', function(w, req)
    return w:json({message = 'Hello World!'})
end)

The parameter can be any JSON type, including table, string, boolean,
or number, and nil.

routing mixin

Extends request (req) with ability to resolve named routes.

Use mixin to extend request:

local mixin = require 'core.mixin'
local http = require 'http'
mixin(http.Request, http.mixins.routing)

req:path_for(name[, args])

Returns URL path part for route name, optionally substituting path named
segments with args table.

-- URL: /en/user/123
-- route path: {locale}/user/{user_id:i}
req:path_for('user', {user_id='1'})
-- "/en/user/1"

Any route argument not provided by args is taken from req.route_args.

req:absolute_url_for(name[, args])

Returns URL for route name.

-- URL: http://localhost:8080/en/user/123
-- route path: {locale}/user/{user_id:i}
req:path_for('user', {user_id='1'})
-- "http://localhost:8080/en/user/1"

See req:path_for.

Nginx Adapters

Provides integration with Nginx HTTP service using lua-nginx-module.

buffered

This adapter buffers each chunk of w:write(), flushes once application finishes processing HTTP request.

http {
    init_by_lua '
        local adapter = require "http.adapters.nginx.buffered"
        main = adapter(require(os.getenv("app") or "demos.http.hello"))
    ';
    server {
        listen       8080;
        server_name  127.0.0.1;
        location / {
            default_type 'text/plain';
            content_by_lua 'main(ngx)';
        }
    }
}

stream

This adapter asynchronously writes data and will return immediately without
waiting for all the data to be written into the system send buffer.

http {
    init_by_lua '
        local adapter = require "http.adapters.nginx.stream"
        main = adapter(require(os.getenv("app") or "demos.http.hello"))
    ';
    server {
        listen       8080;
        server_name  127.0.0.1;
        location / {
            default_type 'text/plain';
            content_by_lua 'main(ngx)';
        }
    }
}

Validation API Reference

Data validation ensures data quality, that they are both correct and useful.

Model Binder

The model binder converts a table of string values to corresponding Lua type
per target model attribute type. For example, if there is a model with an
attribute message_id and it defaults to numeric value 0, the model binder
attempts to convert source value to number.

Example: binding a message model

local binder = require 'validation.binder'

app:put('', function(w, req)
    local m = {message_id=0, message=''}
    local b = binder.new()
    local values = req.body or req:parse_body()
    if not b:bind(m, values) then
        w:set_status_code(400)
        return w:json(b.errors)
    end
end)

There are several model value adapters accessible in
validation.model.adapters table.

The following table describes behavior of corresponding model value adapter.

Name Description
boolean The nil and empty string "" values are converted to nil. If source value type is not boolean , the adapter converts it to string and checks whenever it equals to 1 or true.
number The nil and empty string "" values are converted to nil. Uses lua tonumber function for conversion. Adds an error message in case the input is not in numeric format.
string The nil returned as nil. If source value type is not string, the adapter uses tostring function for conversion. Trims returned value.

If source value is a table with multiple values, the last one is used.

Model value adapter is a function that satisfies the following contract:

local function my_adapter(value, translations)
    if value == nil then
        return nil, translations:gettext("My error message.")
    end
    return value
end

Where value - the original value, can be a table or a string or any JSON
compatible type.

The model can define specific adapters by using adapters attribute, which is a table per
attribute to adapt.

Any attributes that have no corresponding name in the model are ignored.

binder.new([translations])

The b object by convention corresponds to an intance of the model binder.
Create it by calling binder.new function exported by the validation.binder
module.

local binder = require 'validation.binder'

app:post('', function(w, req)
    local b = binder.new()
end)

It also accepts an optional parameter translations.

The binder object cannot be shared between request because it maintains its
state in the errors property.

Properties

b.errors

This property is a table containing any errors reported either by model value
adapters or validators.

The data contract for errors table uses attribute name as a key and a table
of strings for multiple error messages.

{
  "message": ["Required field cannot be left blank."]
}

Example: report binding errors in JSON

app:post('', function(w, req)
    local m = {message=''}
    local b = binder.new()
    local values = req.body or req:parse_body()
    if not b:bind(m, values) then
        w:set_status_code(400)
        return w:json(b.errors)
    end
end)

Methods

b:bind(model, values)

Binds values to model. Returns a boolean value indicating whenever the
binding succeeds or not. In the later case the b.errors table contains any
errors reported.

The b.errors table is populated with all errors per model attributes.

b:validate(model, validator)

Validates model using validator. Returns a boolean value if validation is
passed or not. Similar to bind method, the b.errors table contains
validation errors.

Example: bind model to request body and validate

local binder = require 'validation.binder'
local validator = require 'validation.validator'
local length = require 'validation.rules.length'
local required = require 'validation.rules.required'

local greeting_validator = validator.new {
    author = {required, length{max=20}},
    message = {required, length{min=5}, length{max=512}}
}

-- ...

app:post('', function(w, req)
    local m = {author='', message=''}
    local b = binder.new()
    local values = req.body or req:parse_body()
    if not b:bind(m, values) or
            not b:validate(m, greeting_validator) then
        w:set_status_code(400)
        return w:json(b.errors)
    end
end)

Combine several calls to b.validate to reuse validators between models.

Validator

validator.new(mapping)

The object with suffix _validator by convention corresponds to the model
validator. Create it by calling validator.new function exported by the
validation.validator module:

local validator = require 'validation.validator'

local greeting_validator = validator.new {
}

It accepts the table mapping which designates which model attributes to be
validated by particular validation rules.

Example: greeting validator

local validator = require 'validation.validator'
local length = require 'validation.rules.length'
local required = require 'validation.rules.required'

local greeting_validator = validator.new {
    message = {required, length{min=5}, length{max=512}}
}

You can reuse domain validation rules between validators.

local message_rules = {required, length{min=5}, length{max=512}}

local greeting_validator = validator.new {
    message = message_rules
}

Use validator object by passing it to binder validate method.

local b = binder.new()
if not b:validate(m, greeting_validator) then
    -- see b.errors
end

The validator object is stateless and can be reused.

Mixins

set_error

Extends with ability to set validation error, assumes self.errors.

Example: create account

local mixin = require 'core.mixin'
local validation = require 'validation'

local MembershipService = mixin({}, validation.mixins.set_error)

function MembershipService:create_account(r)
    if self:has_account(r.username) then
        return self:set_error(
            'The user with such username is already registered.',
            'username')
    end
    if not self:add_account(r) then
        return self:set_error(
            'The system was unable to create an account for you.')
    end
    return true
end

The self.errors is populated with the field error in the first case.

{
  "username": ["The user with such username is already registered."]
}

and with general error in the later one.

{
  "__ERROR__": ["The system was unable to create an account for you."]
}

The use of __ERROR__ key is solely by convention.

The self.errors table can be encoded to json so UI can display the error
to the user. For example, the field error displayed below the input box,
while general error as a message at the top part of the input form.

validation

Extends with ability to perform model validation, assumes self.errors and
self:get_locale().

local class = require 'core.class'
local mixin = require 'core.mixin'
local web = require 'web'

mixin(BaseHandler, web.mixins.validation)

local MessageHandler = class(BaseHandler, {
  post = function(self)
      local m = {author='', message=''}
      self.errors = {}
      if not self:update_model(m) or
              not self:validate(m, greeting_validator) then
          return self:json_errors()
      end
      return self:json(m)
  end
})

see a complete example here.

Rules

Validation rule is a module that satisfies the following contract:

local mt = {__index = {
    msg = 'My validation error message.',
    validate = function(self, value, model, translations)
        if not value then
            return translations:gettext(self.msg)
        end
        return nil
    end
}}

return function (options)
    return setmetatable(options, mt)
end

Where value - the value to be validated, like string, number, etc. Returns
nil if there is no error, otherwise an error message.

Use an optional parameter msg in table options to override the default
error message.

Combine several validation rules using a table.

local greeting_validator = validator.new {
    message = {required, length{min=5}, length{max=512}}
}

Validation rules are checked from left to right until a first fail.

bytes{min, max}

The length of the raw byte string value of the model attribute must match the
specified boundaries.

local validator = require 'validation.validator'
local bytes = require 'validation.rules.bytes'

local message_validator = validator.new {
    message = {bytes{max=512}}
}

Use one of the optional min or max to specify the boundaries.

compare{equal, not_equal}

Compares value of the model with the one named as an argument.

local validator = require 'validation.validator'
local compare = require 'validation.rules.compare'

local password_validator = validator.new {
    password = {compare{equal='confirm_password'}}
}

Use one of the optional equal or not_equal to specify the model attribute to
compare.

email

Checks whenever the value corresponds to a valid email address.

local validator = require 'validation.validator'
local email = require 'validation.rules.email'

local password_validator = validator.new {
    alternate_email = {email()}
}

Use an optional parameter msg to override the error message.

length{min, max}

The length of the UTF8 string value of the model attribute must match the
specified boundaries.

local validator = require 'validation.validator'
local length = require 'validation.rules.length'

local message_validator = validator.new {
    message = {length{max=512}}
}

Use one of the optional min or max to specify the boundaries.

pattern{pattern, plain, negated}

The value must match the Lua pattern expression.

local validator = require 'validation.validator'
local pattern = require 'validation.rules.pattern'

local id_validator = validator.new {
    id = {pattern{"%s+"}}
}

Use one of the optional plain or negated to specify desired behavior.

range{min, max}

The numeric value must match the specified boundaries.

local validator = require 'validation.validator'
local range = require 'validation.rules.range'

local age_validator = validator.new {
    age = {length{min=21}}
}

Use one of the optional min or max to specify the boundaries.

required

The value must not be null, an empty string or zero.

local validator = require 'validation.validator'
local required = require 'validation.rules.required'

local user_validator = validator.new {
    username = {required}
}

Use an optional parameter msg to override the error message.

succeed

Always succeeds regardless value supplied.

Web API Reference

Application

Middlewares

routing

Mixins

authcookie

json

locale

model

principal

routing

Tools

lurl

lurl is a tool that allows sending a request to an application and display response.

Usage: lurl [options...] <app> <path>
Options:
 -X COMMAND     Specify request command to use, e.g. POST
 -I             Fetch the headers only
 -H LINE        Pass custom header LINE, e.g. 'Accept: application/json'
 -d DATA        Request body data, e.g. '{"msg":"hello"}', 'msg=hello'
 -b             Issue a number of requests through iterations
 -v             Make the operation more talkative

Basic usage:

lurl -v demos/http/hello.lua /

Output:

req: {
    ["body"] = {},
    ["headers"] = {
        ["accept"] = "*/*",
        ["host"] = "localhost:8080",
        ["user-agent"] = "lurl/scm-0"
    },
    ["method"] = "GET",
    ["path"] = "/",
    ["query"] = {},
    ["route_args"] = {}
}
w: {
    ["buffer"] = {
        "Hello World!\
"
    },
    ["headers"] = {}
}
Docker Pull Command
Owner
akorn
Source Repository