forked from josephg/ShareJS
/
rest.coffee
132 lines (111 loc) · 4.03 KB
/
rest.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# A REST-ful frontend to the OT server.
#
# See the docs for details and examples about how the protocol works.
http = require 'http'
url = require 'url'
connect = require 'connect'
send403 = (res, message = 'Forbidden\n') ->
res.writeHead 403, {'Content-Type': 'text/plain'}
res.end message
send404 = (res, message = '404: Your document could not be found.\n') ->
res.writeHead 404, {'Content-Type': 'text/plain'}
res.end message
sendError = (res, message) ->
if message is 'forbidden'
send403 res
else if message is 'Document does not exist'
send404 res
else
#console.warn "REST server does not know how to send error: '#{message}'"
res.writeHead 500, {'Content-Type': 'text/plain'}
res.end "#{message}\n"
send400 = (res, message) ->
res.writeHead 400, {'Content-Type': 'text/plain'}
res.end message
send200 = (res, message = "OK\n") ->
res.writeHead 200, {'Content-Type': 'text/plain'}
res.end message
sendJSON = (res, obj) ->
res.writeHead 200, {'Content-Type': 'application/json'}
res.end JSON.stringify(obj) + '\n'
# Callback is only called if the object was indeed JSON
expectJSONObject = (req, res, callback) ->
pump req, (data) ->
try
obj = JSON.parse data
catch error
send400 res, 'Supplied JSON invalid'
return
callback(obj)
pump = (req, callback) ->
data = ''
req.on 'data', (chunk) -> data += chunk
req.on 'end', () -> callback(data)
# connect.router will be removed in connect 2.0 - this code will have to be rewritten or
# more libraries pulled in.
# https://github.com/senchalabs/connect/issues/262
router = (app, createClient, options) ->
auth = (req, res, next) ->
data =
headers: req.headers
remoteAddress: req.connection.remoteAddress
createClient data, (error, client) ->
if client
req._client = client
next()
else
sendError res, error
# GET returns the document snapshot. The version and type are sent as headers.
# I'm not sure what to do with document metadata - it is inaccessable for now.
app.get '/doc/:name', auth, (req, res) ->
req._client.getSnapshot req.params.name, (error, doc) ->
if doc
res.setHeader 'X-OT-Type', doc.type.name
res.setHeader 'X-OT-Version', doc.v
if typeof doc.snapshot == 'string'
send200 res, doc.snapshot
else
sendJSON res, doc.snapshot
else
sendError res, error
# Put is used to create a document. The contents are a JSON object with {type:TYPENAME, meta:{...}}
app.put '/doc/:name', auth, (req, res) ->
expectJSONObject req, res, (obj) ->
type = obj?.type
meta = obj?.meta
unless typeof type == 'string' and (meta == undefined or typeof meta == 'object')
send400 res, 'Type invalid'
else
req._client.create req.params.name, type, meta, (error) ->
if error
sendError res, error
else
send200 res
# POST submits an op to the document.
app.post '/doc/:name', auth, (req, res) ->
query = url.parse(req.url, true).query
version = if query?.v?
parseInt query?.v
else
parseInt req.headers['x-ot-version']
unless version? and version >= 0
send400 res, 'Version required - attach query parameter ?v=X on your URL or set the X-OT-Version header'
else
expectJSONObject req, res, (obj) ->
opData = {v:version, op:obj, meta:{source:req.socket.remoteAddress}}
req._client.submitOp req.params.name, opData, (error, newVersion) ->
if error?
sendError res, error
else
sendJSON res, {v:newVersion}
app.delete '/doc/:name', auth, (req, res) ->
req._client.delete req.params.name, (error) ->
if error
sendError res, error
else
send200 res
# Attach the frontend to the supplied http.Server.
#
# As of sharejs 0.4.0, options is ignored. To control the deleting of documents, specify an auth() function.
module.exports = (createClient, options) ->
connect.router (app) -> router(app, createClient, options)