http.snippets['/health'] = {'Health service', '<p>UP!</p>'}
+How to expose RESTful services
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A RESTful service is likely to respond differently to different type of methods and requests,
+there are three things that you can do in a service handler to send back results.
+First is to just send whatever you want to send back, it has to respect MIME type that the service
+declared in the endpoint definition. The response code would then be ``200 OK``, any non-string
+responses will be packed to JSON. Alternatively, you can respond with a number corresponding to
+the HTTP response code or send headers and body yourself.
+
+.. code-block:: lua
+
+ -- Our upvalue
+ local value = 42
+
+ -- Expose the service
+ http.endpoints['/service'] = {'application/json',
+ function (h, stream)
+ -- Get request method and deal with it properly
+ local m = h:get(':method')
+ local path = h:get(':path')
+ log('[service] method %s path %s', m, path)
+ -- Return table, response code will be '200 OK'
+ if m == 'GET' then
+ return {key = path, value = value}
+ -- Save body, perform check and either respond with 505 or 200 OK
+ elseif m == 'POST' then
+ local data = stream:get_body_as_string()
+ if not tonumber(data) then
+ return 505
+ end
+ value = tonumber(data)
+ -- Unsupported method, return 405 Method not allowed
+ else
+ return 405
+ end
+ end}
+
+In some cases you might need to send back your own headers instead of default provided by HTTP handler,
+you can do this, but then you have to return ``false`` to notify handler that it shouldn't try to generate
+a response.
+
+.. code-block:: lua
+
+ local headers = require('http.headers')
+ function (h, stream)
+ -- Send back headers
+ local hsend = headers.new()
+ hsend:append(':status', '200')
+ hsend:append('content-type', 'binary/octet-stream')
+ assert(stream:write_headers(hsend, false))
+ -- Send back data
+ local data = 'binary-data'
+ assert(stream:write_chunk(data, true))
+ -- Disable default handler action
+ return false
+ end
+
How to expose more interfaces
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-- Export HTTP service page snippets
M.snippets = {}
--- Serve GET requests, we only support a fixed
--- number of endpoints that are actually preloaded
--- in memory or constructed on request
-local function serve_get(h, stream)
+-- Serve known requests, for methods other than GET
+-- the endpoint must be a closure and not a preloaded string
+local function serve(h, stream)
local hsend = headers.new()
local path = h:get(':path')
local entry = M.endpoints[path]
-- Unpack MIME and data
- local mime, data
+ local mime, data, err
if entry then
mime, data = unpack(entry)
end
-- Get string data out of service endpoint
if type(data) == 'function' then
- data = data(h, stream)
+ data, err = data(h, stream)
-- Handler doesn't provide any data
if data == false then return end
+ if type(data) == 'number' then return tostring(data) end
+ -- Methods other than GET require handler to be closure
+ elseif h:get(':method') ~= 'GET' then
+ return '501'
end
if type(data) == 'table' then data = tojson(data) end
if not mime or type(data) ~= 'string' then
- hsend:append(':status', '404')
- assert(stream:write_headers(hsend, true))
+ return '404'
else
-- Serve content type appropriately
hsend:append(':status', '200')
end
ws:close()
return
- -- Handle HTTP method appropriately
- elseif m == 'GET' then
- serve_get(h, stream)
else
- -- Method is not supported
- local hsend = headers.new()
- hsend:append(':status', '500')
- assert(stream:write_headers(hsend, true))
+ local ok, err = pcall(serve, h, stream)
+ if not ok or err then
+ log('[http] %s %s: %s', m, path, err or '500')
+ -- Method is not supported
+ local hsend = headers.new()
+ hsend:append(':status', err or '500')
+ assert(stream:write_headers(hsend, true))
+ end
end
stream:shutdown()
end