PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

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

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