summaryrefslogtreecommitdiffstats
path: root/server.lua
diff options
context:
space:
mode:
Diffstat (limited to 'server.lua')
-rw-r--r--server.lua115
1 files changed, 115 insertions, 0 deletions
diff --git a/server.lua b/server.lua
new file mode 100644
index 0000000..11bfe55
--- /dev/null
+++ b/server.lua
@@ -0,0 +1,115 @@
+--[[
+Copyright (c) 2012 Kaarle Ritvanen
+See LICENSE file for license details
+--]]
+
+require 'acf'
+local isinstance = acf.object.isinstance
+
+require 'json'
+require 'stringy'
+
+
+local function handle(env, txn, path)
+ local method = env.REQUEST_METHOD
+ local data
+ if env.CONTENT_LENGTH then
+ data = json.decode(env.input:read(env.CONTENT_LENGTH))
+ end
+
+ 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 implement transactions as threads or store their state in
+-- shared storage
+local last_id = 0
+local txns = {}
+
+
+return function(env)
+ local method = env.REQUEST_METHOD
+ local path = env.REQUEST_URI
+
+ -- TODO login session management
+
+ local txn_id = tonumber(env.HTTP_X_ACF_TRANSACTION_ID)
+ local txn
+ if txn_id then
+ txn = txns[txn_id]
+ if not txn then return 404 end
+ else txn = acf.transaction.start() end
+
+ if stringy.startswith(path, '/config/') then
+ -- TODO catch and forward relevant errors to the client
+ local code, hdr, body = handle(env,
+ txn,
+ string.sub(path, 8, -1))
+ if not txn_id and method ~= 'GET' and code == 200 then
+ txn:commit()
+ end
+ return code, hdr, body
+ 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 200
+ end
+
+ if method == 'DELETE' then return 405 end
+
+ last_id = last_id + 1
+ local txn_id = last_id
+ txns[txn_id] = txn
+ return 200, {['X-ACF-Transaction-ID']=txn_id}
+ end
+
+ return 404
+ end