HttpController.coffee 7.8 KB

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