PageRenderTime 39ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/deps/npm/node_modules/npm-registry-client/lib/request.js

https://bitbucket.org/paulscott56/node
JavaScript | 277 lines | 217 code | 40 blank | 20 comment | 77 complexity | fbc5e56afd5b8de1a915bb9b3597d079 MD5 | raw file
  1. module.exports = regRequest
  2. var url = require("url")
  3. , fs = require("graceful-fs")
  4. , rm = require("rimraf")
  5. , asyncMap = require("slide").asyncMap
  6. , Stream = require("stream").Stream
  7. , request = require("request")
  8. , retry = require("retry")
  9. function regRequest (method, where, what, etag, nofollow, cb_) {
  10. if (typeof cb_ !== "function") cb_ = nofollow, nofollow = false
  11. if (typeof cb_ !== "function") cb_ = etag, etag = null
  12. if (typeof cb_ !== "function") cb_ = what, what = null
  13. if (!this.registry) return cb(new Error(
  14. "No registry url provided: " + method + " " + where))
  15. // Since there are multiple places where an error could occur,
  16. // don't let the cb be called more than once.
  17. var errState = null
  18. function cb (er) {
  19. if (errState) return
  20. if (er) errState = er
  21. cb_.apply(null, arguments)
  22. }
  23. if (where.match(/^\/?favicon.ico/)) {
  24. return cb(new Error("favicon.ico isn't a package, it's a picture."))
  25. }
  26. var registry = this.registry
  27. var adduserChange = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)\/-rev/
  28. , adduserNew = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)/
  29. , nu = where.match(adduserNew)
  30. , uc = where.match(adduserChange)
  31. , isUpload = what || this.alwaysAuth
  32. , isDel = method === "DELETE"
  33. , authRequired = isUpload && !nu || uc || isDel
  34. // resolve to a full url on the registry
  35. if (!where.match(/^https?:\/\//)) {
  36. this.log.verbose("url raw", where)
  37. var q = where.split("?")
  38. where = q.shift()
  39. q = q.join("?")
  40. if (where.charAt(0) !== "/") where = "/" + where
  41. where = "." + where.split("/").map(function (p) {
  42. p = p.trim()
  43. if (p.match(/^org.couchdb.user/)) {
  44. return p.replace(/\//g, encodeURIComponent("/"))
  45. }
  46. return encodeURIComponent(p)
  47. }).join("/")
  48. if (q) where += "?" + q
  49. this.log.verbose("url resolving", [registry, where])
  50. where = url.resolve(registry, where)
  51. this.log.verbose("url resolved", where)
  52. }
  53. var remote = url.parse(where)
  54. , auth = this.auth
  55. if (authRequired && !this.alwaysAuth) {
  56. var couch = this.couchLogin
  57. , token = couch && (this.token || couch.token)
  58. , validToken = token && couch.valid(token)
  59. if (!validToken) token = null
  60. else this.token = token
  61. if (couch && !token) {
  62. // login to get a valid token
  63. var a = { name: this.username, password: this.password }
  64. var args = arguments
  65. return this.couchLogin.login(a, function (er, cr, data) {
  66. if (er || !couch.valid(couch.token)) {
  67. er = er || new Error('login error')
  68. return cb(er, cr, data)
  69. }
  70. this.token = this.couchLogin.token
  71. return regRequest.call(this, method, where, what, etag, nofollow, cb_)
  72. }.bind(this))
  73. }
  74. }
  75. // now we either have a valid token, or an auth.
  76. if (authRequired && !auth && !token) {
  77. return cb(new Error(
  78. "Cannot insert data into the registry without auth"))
  79. }
  80. if (auth && !token) {
  81. remote.auth = new Buffer(auth, "base64").toString("utf8")
  82. }
  83. // Tuned to spread 3 attempts over about a minute.
  84. // See formula at <https://github.com/tim-kos/node-retry>.
  85. var operation = retry.operation({
  86. retries: this.retries,
  87. factor: this.retryFactor,
  88. minTimeout: this.retryMinTimeout,
  89. maxTimeout: this.retryMaxTimeout
  90. })
  91. var self = this
  92. operation.attempt(function (currentAttempt) {
  93. self.log.info("retry", "registry request attempt " + currentAttempt
  94. + " at " + (new Date()).toLocaleTimeString())
  95. makeRequest.call(self, method, remote, where, what, etag, nofollow, token
  96. , function (er, parsed, raw, response) {
  97. // Only retry on 408, 5xx or no `response`.
  98. var statusCode = response && response.statusCode
  99. var reauth = statusCode === 401
  100. var timeout = statusCode === 408
  101. var serverError = statusCode >= 500
  102. var statusRetry = !statusCode || timeout || serverError
  103. if (reauth && this.auth && this.token) {
  104. this.token = null
  105. this.couchLogin.token = null
  106. return regRequest.call(this, method, where, what, etag, nofollow, cb_)
  107. }
  108. if (er && statusRetry && operation.retry(er)) {
  109. self.log.info("retry", "will retry, error on last attempt: " + er)
  110. return
  111. }
  112. cb.apply(null, arguments)
  113. }.bind(this))
  114. }.bind(this))
  115. }
  116. function makeRequest (method, remote, where, what, etag, nofollow, tok, cb_) {
  117. var cbCalled = false
  118. function cb () {
  119. if (cbCalled) return
  120. cbCalled = true
  121. cb_.apply(null, arguments)
  122. }
  123. var opts = { url: remote
  124. , method: method
  125. , ca: this.ca
  126. , strictSSL: this.strictSSL }
  127. , headers = opts.headers = {}
  128. if (etag) {
  129. this.log.verbose("etag", etag)
  130. headers[method === "GET" ? "if-none-match" : "if-match"] = etag
  131. }
  132. if (tok) {
  133. headers.cookie = 'AuthSession=' + tok.AuthSession
  134. }
  135. headers.accept = "application/json"
  136. headers["user-agent"] = this.userAgent
  137. opts.proxy = remote.protocol === "https:"
  138. ? this.httpsProxy : this.proxy
  139. // figure out wth 'what' is
  140. if (what) {
  141. if (Buffer.isBuffer(what) || typeof what === "string") {
  142. opts.body = what
  143. headers["content-type"] = "application/json"
  144. headers["content-length"] = Buffer.byteLength(what)
  145. } else if (what instanceof Stream) {
  146. headers["content-type"] = "application/octet-stream"
  147. if (what.size) headers["content-length"] = what.size
  148. } else {
  149. delete what._etag
  150. opts.json = what
  151. }
  152. }
  153. if (nofollow) {
  154. opts.followRedirect = false
  155. }
  156. this.log.http(method, remote.href || "/")
  157. var done = requestDone.call(this, method, where, cb)
  158. var req = request(opts, done)
  159. req.on("error", cb)
  160. req.on("socket", function (s) {
  161. s.on("error", cb)
  162. })
  163. if (what && (what instanceof Stream)) {
  164. what.pipe(req)
  165. }
  166. }
  167. // cb(er, parsed, raw, response)
  168. function requestDone (method, where, cb) {
  169. return function (er, response, data) {
  170. if (er) return cb(er)
  171. this.log.http(response.statusCode, url.parse(where).href)
  172. var parsed
  173. if (Buffer.isBuffer(data)) {
  174. data = data.toString()
  175. }
  176. if (data && typeof data === "string" && response.statusCode !== 304) {
  177. try {
  178. parsed = JSON.parse(data)
  179. } catch (ex) {
  180. ex.message += "\n" + data
  181. this.log.verbose("bad json", data)
  182. this.log.error("registry", "error parsing json")
  183. return cb(ex, null, data, response)
  184. }
  185. } else if (data) {
  186. parsed = data
  187. data = JSON.stringify(parsed)
  188. }
  189. // expect data with any error codes
  190. if (!data && response.statusCode >= 400) {
  191. return cb( response.statusCode + " "
  192. + require("http").STATUS_CODES[response.statusCode]
  193. , null, data, response )
  194. }
  195. var er = null
  196. if (parsed && response.headers.etag) {
  197. parsed._etag = response.headers.etag
  198. }
  199. if (parsed && parsed.error && response.statusCode >= 400) {
  200. var w = url.parse(where).pathname.substr(1)
  201. if (!w.match(/^-/) && parsed.error === "not_found") {
  202. w = w.split("/")
  203. name = w[w.indexOf("_rewrite") + 1]
  204. er = new Error("404 Not Found: "+name)
  205. er.code = "E404"
  206. er.pkgid = name
  207. } else {
  208. er = new Error(
  209. parsed.error + " " + (parsed.reason || "") + ": " + w)
  210. }
  211. } else if (method !== "HEAD" && method !== "GET") {
  212. // invalidate cache
  213. // This is irrelevant for commands that do etag caching, but
  214. // ls and view also have a timed cache, so this keeps the user
  215. // from thinking that it didn't work when it did.
  216. // Note that failure is an acceptable option here, since the
  217. // only result will be a stale cache for some helper commands.
  218. var path = require("path")
  219. , p = url.parse(where).pathname.split("/")
  220. , _ = "/"
  221. , caches = p.map(function (part) {
  222. return _ = path.join(_, part)
  223. }).map(function (cache) {
  224. return path.join(this.cache, cache, ".cache.json")
  225. }, this)
  226. // if the method is DELETE, then also remove the thing itself.
  227. // Note that the search index is probably invalid. Whatever.
  228. // That's what you get for deleting stuff. Don't do that.
  229. if (method === "DELETE") {
  230. p = p.slice(0, p.indexOf("-rev"))
  231. caches.push(path.join(this.cache, p.join("/")))
  232. }
  233. asyncMap(caches, rm, function () {})
  234. }
  235. return cb(er, parsed, data, response)
  236. }.bind(this)
  237. }