From f272cb4c51cb2bb3752269faf431bcf4bfbc0686 Mon Sep 17 00:00:00 2001 From: Kaarle Ritvanen Date: Thu, 7 Mar 2013 07:14:03 +0000 Subject: forward relevant error messages to client --- server.lua | 200 +++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 115 insertions(+), 85 deletions(-) (limited to 'server.lua') diff --git a/server.lua b/server.lua index c6fe586..cbbc82f 100644 --- a/server.lua +++ b/server.lua @@ -10,50 +10,6 @@ require 'json' require 'stringy' -local function handle(txn, method, path, data) - local parent, name - if path ~= '/' then - parent = txn:search(acf.path.parent(path)) - name = acf.path.name(path) - end - - if method == 'GET' then - local obj = txn:search(path) - if obj == nil then return 404 end - - local res - - if isinstance(obj, acf.model.node.TreeNode) then - local node = {} - for _, k in ipairs(acf.model.node.members(obj)) do - local v = obj[k] - if isinstance(v, acf.model.node.TreeNode) then - v = acf.model.node.path(v) - end - node[k] = v - end - res = {data=node, meta=acf.model.node.meta(obj)} - - else res = {data=obj, meta=acf.model.node.mmeta(parent, name)} end - - local function f() coroutine.yield(json.encode(res)) end - return 200, {['Content-Type']='application/json'}, coroutine.wrap(f) - end - - if method == 'DELETE' then - parent[name] = nil - - elseif method == 'PUT' then - parent[name] = data - - -- TODO implement POST for invoking object-specific actions - - else return 405 end - - return 200 -end - - -- TODO shared storage for login sessions local last_sid = 0 local sessions = {} @@ -70,23 +26,50 @@ return function(env) local method = env.REQUEST_METHOD local path = env.REQUEST_URI + local function wrap(code, headers, res) + if not headers then headers = {} end + + local ctype + if type(res) == 'table' then + ctype = 'application/json' + res = json.encode(res) + else + ctype = 'text/plain' + if not res then res = '' end + end + headers['Content-Type'] = ctype + + return code, headers, coroutine.wrap( + function() coroutine.yield(res) end + ) + end + local data if env.CONTENT_LENGTH then - data = json.decode(env.input:read(env.CONTENT_LENGTH)) + local success + success, data = pcall( + json.decode, + env.input:read(env.CONTENT_LENGTH) + ) + if not success then + return wrap(400, nil, 'Request not in JSON format') + end end local sid = tonumber(env.HTTP_X_ACF_AUTH_TOKEN) local user, txn_id if sid then user = sessions[sid] - if not user then return 401 end + if not user then return wrap(401) end txn_id = tonumber(env.HTTP_X_ACF_TRANSACTION_ID) end local txn if txn_id then txn = txns[txn_id] - if not txn then return 404 end + if not txn then + return wrap(400, nil, 'Invalid transaction ID') + end else txn = acf.transaction.start() end local function fetch_user(name) @@ -94,66 +77,113 @@ return function(env) end if user then fetch_user(user) - if not user then return 401 end + if not user then return wrap(401) end end if path == '/login' then if method == 'POST' then - if not data.username or not data.password then return 401 end + if not data.username or not data.password then + return wrap(401) + end fetch_user(data.username) if user and user:check_password(data.password) then last_sid = last_sid + 1 local sid = last_sid sessions[sid] = data.username - return 200, {['X-ACF-Auth-Token']=sid} + return wrap(204, {['X-ACF-Auth-Token']=sid}) end - return 401 + return wrap(401) end - if not user then return 401 end + if not user then return wrap(401) end if method == 'DELETE' then sessions[sid] = nil - return 200 + return wrap(204) end - return 405 - end - - if not user then return 401 end - - if stringy.startswith(path, '/config/') then - -- TODO catch and forward relevant errors to the client - local code, hdr, body = handle(txn, - method, - string.sub(path, 8, -1), - data) - if not txn_id and method ~= 'GET' and code == 200 then - txn:commit() - end - return code, hdr, body + return wrap(405) end - if path == '/' then - if method == 'GET' then return 301, {['Location']='/browser/'} end + if not user then return wrap(401) end + + local success, code, hdr, res = acf.call( + function() + if stringy.startswith(path, '/config/') then + path = string.sub(path, 8, -1) + + local parent, name + if path ~= '/' then + parent = txn:search(acf.path.parent(path)) + name = acf.path.name(path) + end + + if method == 'GET' then + local obj = txn:search(path) + if obj == nil then return 404 end + + local res + + if isinstance(obj, acf.model.node.TreeNode) then + local node = {} + for _, k in ipairs(acf.model.node.members(obj)) do + local v = obj[k] + if isinstance(v, acf.model.node.TreeNode) then + v = acf.model.node.path(v) + end + node[k] = v + end + res = {data=node, meta=acf.model.node.meta(obj)} + + else + res = { + data=obj, + meta=acf.model.node.mmeta(parent, name) + } + end + + return 200, nil, res + end + + -- TODO implement POST for invoking object-specific actions + if method == 'DELETE' then parent[name] = nil + elseif method == 'PUT' then parent[name] = data + else return 405 end + + if not txn_id then txn:commit() end + return 205 + end - if not ({DELETE=true, POST=true})[method] then - return 405 - end + if path == '/' then + if method == 'GET' then + return 301, {['Location']='/browser/'} + end + + if not ({DELETE=true, POST=true})[method] then + return 405 + end + + if txn_id then + if method == 'POST' then txn:commit() end + txns[txn_id] = nil + return 204 + end + + if method == 'DELETE' then return 405 end + + last_txn_id = last_txn_id + 1 + local txn_id = last_txn_id + txns[txn_id] = txn + return 204, {['X-ACF-Transaction-ID']=txn_id} + end - if txn_id then - if method == 'POST' then txn:commit() end - txns[txn_id] = nil - return 200 + return 404 end + ) - if method == 'DELETE' then return 405 end - - last_txn_id = last_txn_id + 1 - local txn_id = last_txn_id - txns[txn_id] = txn - return 200, {['X-ACF-Transaction-ID']=txn_id} - end + if success then return wrap(code, hdr, res) end - return 404 + if code.conflict then return wrap(409, nil, code.conflict) end + + return wrap(422, nil, code) end -- cgit v1.2.3