From c27905a9b32d440c6974b414c1f05aa649f703f6 Mon Sep 17 00:00:00 2001 From: Marek Vavrusa Date: Mon, 13 Jun 2016 10:13:08 -0700 Subject: [PATCH] modules/http: added safe stream handler, doc --- modules/http/README.rst | 58 +++++++++++++++++++++++++++++++++++++++++ modules/http/http.lua | 33 ++++++++++++----------- 2 files changed, 76 insertions(+), 15 deletions(-) diff --git a/modules/http/README.rst b/modules/http/README.rst index 9bf2c6b74..2d9cd8d05 100644 --- a/modules/http/README.rst +++ b/modules/http/README.rst @@ -157,6 +157,64 @@ exported restful APIs and subscribe to WebSockets. http.snippets['/health'] = {'Health service', '

UP!

'} +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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/modules/http/http.lua b/modules/http/http.lua index c17bb8eb3..fb81e91cd 100644 --- a/modules/http/http.lua +++ b/modules/http/http.lua @@ -86,28 +86,30 @@ end -- 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') @@ -147,14 +149,15 @@ local function route(endpoints) 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 -- 2.47.2