HttpController.coffee 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. DocumentManager = require "./DocumentManager"
  2. HistoryManager = require "./HistoryManager"
  3. ProjectManager = require "./ProjectManager"
  4. Errors = require "./Errors"
  5. logger = require "logger-sharelatex"
  6. Metrics = require "./Metrics"
  7. ProjectFlusher = require("./ProjectFlusher")
  8. DeleteQueueManager = require("./DeleteQueueManager")
  9. async = require "async"
  10. TWO_MEGABYTES = 2 * 1024 * 1024
  11. module.exports = HttpController =
  12. getDoc: (req, res, next = (error) ->) ->
  13. doc_id = req.params.doc_id
  14. project_id = req.params.project_id
  15. logger.log project_id: project_id, doc_id: doc_id, "getting doc via http"
  16. timer = new Metrics.Timer("http.getDoc")
  17. if req.query?.fromVersion?
  18. fromVersion = parseInt(req.query.fromVersion, 10)
  19. else
  20. fromVersion = -1
  21. DocumentManager.getDocAndRecentOpsWithLock project_id, doc_id, fromVersion, (error, lines, version, ops, ranges, pathname) ->
  22. timer.done()
  23. return next(error) if error?
  24. logger.log project_id: project_id, doc_id: doc_id, "got doc via http"
  25. if !lines? or !version?
  26. return next(new Errors.NotFoundError("document not found"))
  27. res.json
  28. id: doc_id
  29. lines: lines
  30. version: version
  31. ops: ops
  32. ranges: ranges
  33. pathname: pathname
  34. _getTotalSizeOfLines: (lines) ->
  35. size = 0
  36. for line in lines
  37. size += (line.length + 1)
  38. return size
  39. getProjectDocsAndFlushIfOld: (req, res, next = (error) ->) ->
  40. project_id = req.params.project_id
  41. projectStateHash = req.query?.state
  42. # exclude is string of existing docs "id:version,id:version,..."
  43. excludeItems = req.query?.exclude?.split(',') or []
  44. logger.log project_id: project_id, exclude: excludeItems, "getting docs via http"
  45. timer = new Metrics.Timer("http.getAllDocs")
  46. excludeVersions = {}
  47. for item in excludeItems
  48. [id,version] = item?.split(':')
  49. excludeVersions[id] = version
  50. logger.log {project_id: project_id, projectStateHash: projectStateHash, excludeVersions: excludeVersions}, "excluding versions"
  51. ProjectManager.getProjectDocsAndFlushIfOld project_id, projectStateHash, excludeVersions, (error, result) ->
  52. timer.done()
  53. if error instanceof Errors.ProjectStateChangedError
  54. res.send 409 # conflict
  55. else if error?
  56. return next(error)
  57. else
  58. logger.log project_id: project_id, result: ("#{doc._id}:#{doc.v}" for doc in result), "got docs via http"
  59. res.send result
  60. clearProjectState: (req, res, next = (error) ->) ->
  61. project_id = req.params.project_id
  62. timer = new Metrics.Timer("http.clearProjectState")
  63. logger.log project_id: project_id, "clearing project state via http"
  64. ProjectManager.clearProjectState project_id, (error) ->
  65. timer.done()
  66. if error?
  67. return next(error)
  68. else
  69. res.send 200
  70. setDoc: (req, res, next = (error) ->) ->
  71. doc_id = req.params.doc_id
  72. project_id = req.params.project_id
  73. {lines, source, user_id, undoing} = req.body
  74. lineSize = HttpController._getTotalSizeOfLines(lines)
  75. if lineSize > TWO_MEGABYTES
  76. logger.log {project_id, doc_id, source, lineSize, user_id}, "document too large, returning 406 response"
  77. return res.send 406
  78. logger.log {project_id, doc_id, lines, source, user_id, undoing}, "setting doc via http"
  79. timer = new Metrics.Timer("http.setDoc")
  80. DocumentManager.setDocWithLock project_id, doc_id, lines, source, user_id, undoing, (error) ->
  81. timer.done()
  82. return next(error) if error?
  83. logger.log project_id: project_id, doc_id: doc_id, "set doc via http"
  84. res.send 204 # No Content
  85. flushDocIfLoaded: (req, res, next = (error) ->) ->
  86. doc_id = req.params.doc_id
  87. project_id = req.params.project_id
  88. logger.log project_id: project_id, doc_id: doc_id, "flushing doc via http"
  89. timer = new Metrics.Timer("http.flushDoc")
  90. DocumentManager.flushDocIfLoadedWithLock project_id, doc_id, (error) ->
  91. timer.done()
  92. return next(error) if error?
  93. logger.log project_id: project_id, doc_id: doc_id, "flushed doc via http"
  94. res.send 204 # No Content
  95. deleteDoc: (req, res, next = (error) ->) ->
  96. doc_id = req.params.doc_id
  97. project_id = req.params.project_id
  98. skip_flush = req.query.skip_flush == 'true'
  99. timer = new Metrics.Timer("http.deleteDoc")
  100. if skip_flush
  101. logger.log project_id: project_id, doc_id: doc_id, "deleting doc skipping flush via http (contents may be lost)"
  102. # Warning: This action is destructive. Skipping the flush will lose
  103. # contents that have not been flushed yet. Use this to fix a document in a
  104. # bad state that can't be flushed anyway.
  105. DocumentManager.deleteDocWithLock project_id, doc_id, (error) ->
  106. timer.done()
  107. return next(error) if error?
  108. logger.log project_id: project_id, doc_id: doc_id, "deleted doc via http"
  109. res.send 204 # No Content
  110. else
  111. logger.log project_id: project_id, doc_id: doc_id, "deleting doc via http"
  112. DocumentManager.flushAndDeleteDocWithLock project_id, doc_id, (error) ->
  113. timer.done()
  114. # There is no harm in flushing project history if the previous call
  115. # failed and sometimes it is required
  116. HistoryManager.flushProjectChangesAsync project_id
  117. return next(error) if error?
  118. logger.log project_id: project_id, doc_id: doc_id, "deleted doc via http"
  119. res.send 204 # No Content
  120. flushProject: (req, res, next = (error) ->) ->
  121. project_id = req.params.project_id
  122. logger.log project_id: project_id, "flushing project via http"
  123. timer = new Metrics.Timer("http.flushProject")
  124. ProjectManager.flushProjectWithLocks project_id, (error) ->
  125. timer.done()
  126. return next(error) if error?
  127. logger.log project_id: project_id, "flushed project via http"
  128. res.send 204 # No Content
  129. deleteProject: (req, res, next = (error) ->) ->
  130. project_id = req.params.project_id
  131. logger.log project_id: project_id, "deleting project via http"
  132. options = {}
  133. options.background = true if req.query?.background # allow non-urgent flushes to be queued
  134. options.skip_history_flush = true if req.query?.shutdown # don't flush history when realtime shuts down
  135. if req.query?.background
  136. ProjectManager.queueFlushAndDeleteProject project_id, (error) ->
  137. return next(error) if error?
  138. logger.log project_id: project_id, "queue delete of project via http"
  139. res.send 204 # No Content
  140. else
  141. timer = new Metrics.Timer("http.deleteProject")
  142. ProjectManager.flushAndDeleteProjectWithLocks project_id, options, (error) ->
  143. timer.done()
  144. return next(error) if error?
  145. logger.log project_id: project_id, "deleted project via http"
  146. res.send 204 # No Content
  147. deleteMultipleProjects: (req, res, next = (error) ->) ->
  148. project_ids = req.body?.project_ids || []
  149. logger.log project_ids: project_ids, "deleting multiple projects via http"
  150. async.eachSeries project_ids, (project_id, cb) ->
  151. logger.log project_id: project_id, "queue delete of project via http"
  152. ProjectManager.queueFlushAndDeleteProject project_id, cb
  153. , (error) ->
  154. return next(error) if error?
  155. res.send 204 # No Content
  156. acceptChanges: (req, res, next = (error) ->) ->
  157. {project_id, doc_id} = req.params
  158. change_ids = req.body?.change_ids
  159. if !change_ids?
  160. change_ids = [ req.params.change_id ]
  161. logger.log {project_id, doc_id}, "accepting #{ change_ids.length } changes via http"
  162. timer = new Metrics.Timer("http.acceptChanges")
  163. DocumentManager.acceptChangesWithLock project_id, doc_id, change_ids, (error) ->
  164. timer.done()
  165. return next(error) if error?
  166. logger.log {project_id, doc_id}, "accepted #{ change_ids.length } changes via http"
  167. res.send 204 # No Content
  168. deleteComment: (req, res, next = (error) ->) ->
  169. {project_id, doc_id, comment_id} = req.params
  170. logger.log {project_id, doc_id, comment_id}, "deleting comment via http"
  171. timer = new Metrics.Timer("http.deleteComment")
  172. DocumentManager.deleteCommentWithLock project_id, doc_id, comment_id, (error) ->
  173. timer.done()
  174. return next(error) if error?
  175. logger.log {project_id, doc_id, comment_id}, "deleted comment via http"
  176. res.send 204 # No Content
  177. updateProject: (req, res, next = (error) ->) ->
  178. timer = new Metrics.Timer("http.updateProject")
  179. project_id = req.params.project_id
  180. {projectHistoryId, userId, docUpdates, fileUpdates, version} = req.body
  181. logger.log {project_id, docUpdates, fileUpdates, version}, "updating project via http"
  182. ProjectManager.updateProjectWithLocks project_id, projectHistoryId, userId, docUpdates, fileUpdates, version, (error) ->
  183. timer.done()
  184. return next(error) if error?
  185. logger.log project_id: project_id, "updated project via http"
  186. res.send 204 # No Content
  187. resyncProjectHistory: (req, res, next = (error) ->) ->
  188. project_id = req.params.project_id
  189. {projectHistoryId, docs, files} = req.body
  190. logger.log {project_id, docs, files}, "queuing project history resync via http"
  191. HistoryManager.resyncProjectHistory project_id, projectHistoryId, docs, files, (error) ->
  192. return next(error) if error?
  193. logger.log {project_id}, "queued project history resync via http"
  194. res.send 204
  195. flushAllProjects: (req, res, next = (error)-> )->
  196. res.setTimeout(5 * 60 * 1000)
  197. options =
  198. limit : req.query.limit || 1000
  199. concurrency : req.query.concurrency || 5
  200. dryRun : req.query.dryRun || false
  201. ProjectFlusher.flushAllProjects options, (err, project_ids)->
  202. if err?
  203. logger.err err:err, "error bulk flushing projects"
  204. res.send 500
  205. else
  206. res.send project_ids
  207. flushQueuedProjects: (req, res, next = (error) ->) ->
  208. res.setTimeout(10 * 60 * 1000)
  209. options =
  210. limit : req.query.limit || 1000
  211. timeout: 5 * 60 * 1000
  212. min_delete_age: req.query.min_delete_age || 5 * 60 * 1000
  213. DeleteQueueManager.flushAndDeleteOldProjects options, (err, flushed)->
  214. if err?
  215. logger.err err:err, "error flushing old projects"
  216. res.send 500
  217. else
  218. logger.log {flushed: flushed}, "flush of queued projects completed"
  219. res.send {flushed: flushed}