| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- # 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, head = false) ->
- if message == 'forbidden'
- if head
- send403 res, ""
- else
- send403 res
- else if message == 'Document does not exist'
- if head
- send404 res, ""
- else
- send404 res
- else
- console.warn "REST server does not know how to send error: '#{message}'"
- if head
- res.writeHead 500, {'Content-Type': 'text/plain'}
- res.end "Error: #{message}\n"
- else
- res.writeHead 500, {}
- res.end ""
- 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 req.method == "HEAD"
- send200 res, ""
- else
- if typeof doc.snapshot == 'string'
- send200 res, doc.snapshot
- else
- sendJSON res, doc.snapshot
- else
- if req.method == "HEAD"
- sendError res, error, true
- 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)
|