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

265 lines
7.6 KiB
JavaScript
Raw Normal View History

2014-09-25 05:41:07 +08:00
module.exports = regRequest
2014-08-01 00:05:30 +08:00
// npm: means
// 1. https
// 2. send authorization
// 3. content-type is 'application/json' -- metadata
//
var assert = require("assert")
, url = require("url")
, zlib = require("zlib")
, Stream = require("stream").Stream
2014-08-01 00:05:30 +08:00
var request = require("request")
, once = require("once")
2014-08-01 00:05:30 +08:00
function regRequest (uri, params, cb_) {
assert(typeof uri === "string", "must pass uri to request")
assert(params && typeof params === "object", "must pass params to request")
assert(typeof cb_ === "function", "must pass callback to request")
params.method = params.method || "GET"
this.log.verbose("request", "uri", uri)
2012-06-12 00:30:44 +08:00
// Since there are multiple places where an error could occur,
// don't let the cb be called more than once.
2014-09-25 05:41:07 +08:00
var cb = once(cb_)
2012-06-12 00:30:44 +08:00
if (uri.match(/^\/?favicon.ico/)) {
2012-06-12 00:30:44 +08:00
return cb(new Error("favicon.ico isn't a package, it's a picture."))
}
var adduserChange = /\/?-\/user\/org\.couchdb\.user:([^/]+)\/-rev/
, isUserChange = uri.match(adduserChange)
, adduserNew = /\/?-\/user\/org\.couchdb\.user:([^/?]+)$/
, isNewUser = uri.match(adduserNew)
, alwaysAuth = params.auth && params.auth.alwaysAuth
, isDelete = params.method === "DELETE"
, isWrite = params.body || isDelete
2014-09-25 05:41:07 +08:00
if (isUserChange && !isWrite) {
return cb(new Error("trying to change user document without writing(?!)"))
}
2012-06-12 00:30:44 +08:00
2014-09-25 05:41:07 +08:00
// new users can *not* use auth, because they don't *have* auth yet
if (isUserChange) {
this.log.verbose("request", "updating existing user; sending authorization")
params.authed = true
}
else if (isNewUser) {
2014-09-25 05:41:07 +08:00
this.log.verbose("request", "new user, so can't send auth")
params.authed = false
2012-06-12 00:30:44 +08:00
}
2014-09-25 05:41:07 +08:00
else if (alwaysAuth) {
this.log.verbose("request", "always-auth set; sending authorization")
params.authed = true
2014-09-25 05:41:07 +08:00
}
else if (isWrite) {
this.log.verbose("request", "sending authorization for write operation")
params.authed = true
2014-09-25 05:41:07 +08:00
}
else {
// most of the time we don't want to auth
this.log.verbose("request", "no auth needed")
params.authed = false
2012-06-12 00:30:44 +08:00
}
2012-08-22 06:29:03 +08:00
var self = this
2014-09-25 05:41:07 +08:00
this.attempt(function (operation) {
makeRequest.call(self, uri, params, function (er, parsed, raw, response) {
2014-06-06 06:18:15 +08:00
if (!er || (er.message && er.message.match(/^SSL Error/))) {
2012-09-11 00:11:54 +08:00
if (er)
2014-09-25 05:41:07 +08:00
er.code = "ESSL"
2012-09-11 00:11:54 +08:00
return cb(er, parsed, raw, response)
}
// Only retry on 408, 5xx or no `response`.
var statusCode = response && response.statusCode
2013-02-16 02:49:16 +08:00
2012-07-18 02:37:39 +08:00
var timeout = statusCode === 408
var serverError = statusCode >= 500
var statusRetry = !statusCode || timeout || serverError
if (er && statusRetry && operation.retry(er)) {
self.log.info("retry", "will retry, error on last attempt: " + er)
2014-09-25 05:41:07 +08:00
return undefined
}
2014-09-17 06:38:50 +08:00
if (response) {
2014-09-25 05:41:07 +08:00
self.log.verbose("headers", response.headers)
2014-09-17 06:38:50 +08:00
if (response.headers["npm-notice"]) {
2014-09-25 05:41:07 +08:00
self.log.warn("notice", response.headers["npm-notice"])
2014-09-17 06:38:50 +08:00
}
}
cb.apply(null, arguments)
2014-09-25 05:41:07 +08:00
})
})
2012-06-12 00:30:44 +08:00
}
function makeRequest (uri, params, cb_) {
2014-09-25 05:41:07 +08:00
var cb = once(cb_)
var parsed = url.parse(uri)
2014-09-25 05:41:07 +08:00
var headers = {}
2012-06-12 00:30:44 +08:00
2014-09-25 05:41:07 +08:00
// metadata should be compressed
headers["accept-encoding"] = "gzip"
var er = this.authify(params.authed, parsed, headers, params.auth)
2014-09-25 05:41:07 +08:00
if (er) return cb_(er)
2014-09-25 05:41:07 +08:00
var opts = this.initialize(
parsed,
params.method,
2014-09-25 05:41:07 +08:00
"application/json",
headers
)
2012-06-12 00:30:44 +08:00
opts.followRedirect = (typeof params.follow === "boolean" ? params.follow : true)
2014-09-25 05:41:07 +08:00
opts.encoding = null // tell request let body be Buffer instance
2012-06-12 00:30:44 +08:00
if (params.etag) {
this.log.verbose("etag", params.etag)
headers[params.method === "GET" ? "if-none-match" : "if-match"] = params.etag
2014-09-25 05:41:07 +08:00
}
2012-06-12 00:30:44 +08:00
if (params.lastModified && params.method === "GET") {
this.log.verbose("lastModified", params.lastModified)
headers["if-modified-since"] = params.lastModified
}
// figure out wth body is
if (params.body) {
if (Buffer.isBuffer(params.body)) {
opts.body = params.body
2012-06-12 00:30:44 +08:00
headers["content-type"] = "application/json"
headers["content-length"] = params.body.length
}
else if (typeof params.body === "string") {
opts.body = params.body
headers["content-type"] = "application/json"
headers["content-length"] = Buffer.byteLength(params.body)
}
else if (params.body instanceof Stream) {
2012-06-12 00:30:44 +08:00
headers["content-type"] = "application/octet-stream"
if (params.body.size) headers["content-length"] = params.body.size
}
else {
delete params.body._etag
delete params.body._lastModified
opts.json = params.body
2012-06-12 00:30:44 +08:00
}
}
this.log.http("request", params.method, parsed.href || "/")
2012-06-12 00:30:44 +08:00
var done = requestDone.call(this, params.method, uri, cb)
var req = request(opts, decodeResponseBody(done))
2012-06-12 00:30:44 +08:00
req.on("error", cb)
req.on("socket", function (s) {
s.on("error", cb)
})
2012-06-12 00:30:44 +08:00
if (params.body && (params.body instanceof Stream)) {
params.body.pipe(req)
2012-06-12 00:30:44 +08:00
}
}
function decodeResponseBody(cb) {
return function (er, response, data) {
if (er) return cb(er, response, data)
// don't ever re-use connections that had server errors.
// those sockets connect to the Bad Place!
if (response.socket && response.statusCode > 500) {
response.socket.destroy()
}
if (response.headers["content-encoding"] !== "gzip") {
return cb(er, response, data)
}
zlib.gunzip(data, function (er, buf) {
if (er) return cb(er, response, data)
cb(null, response, buf)
})
}
}
2012-06-12 00:30:44 +08:00
// cb(er, parsed, raw, response)
function requestDone (method, where, cb) {
return function (er, response, data) {
if (er) return cb(er)
2013-05-25 05:41:43 +08:00
var urlObj = url.parse(where)
if (urlObj.auth) urlObj.auth = "***"
2013-05-25 05:41:43 +08:00
this.log.http(response.statusCode, url.format(urlObj))
2012-06-12 00:30:44 +08:00
if (Buffer.isBuffer(data)) {
data = data.toString()
}
var parsed
2012-06-12 00:30:44 +08:00
if (data && typeof data === "string" && response.statusCode !== 304) {
try {
parsed = JSON.parse(data)
} catch (ex) {
ex.message += "\n" + data
this.log.verbose("bad json", data)
this.log.error("registry", "error parsing json")
return cb(ex, null, data, response)
}
} else if (data) {
parsed = data
data = JSON.stringify(parsed)
}
// expect data with any error codes
if (!data && response.statusCode >= 400) {
return cb( response.statusCode + " "
+ require("http").STATUS_CODES[response.statusCode]
, null, data, response )
}
2014-06-06 06:18:15 +08:00
er = null
2012-06-12 00:30:44 +08:00
if (parsed && response.headers.etag) {
parsed._etag = response.headers.etag
}
if (parsed && response.headers["last-modified"]) {
parsed._lastModified = response.headers["last-modified"]
}
// for the search endpoint, the "error" property can be an object
if (parsed && parsed.error && typeof parsed.error !== "object" ||
response.statusCode >= 400) {
2012-06-12 00:30:44 +08:00
var w = url.parse(where).pathname.substr(1)
2013-04-12 00:16:47 +08:00
var name
2014-09-25 05:41:07 +08:00
if (!w.match(/^-/)) {
2012-06-12 00:30:44 +08:00
w = w.split("/")
name = w[w.indexOf("_rewrite") + 1]
2014-09-25 05:41:07 +08:00
}
if (!parsed.error) {
er = new Error(
"Registry returned " + response.statusCode +
" for " + method +
" on " + where
)
}
else if (name && parsed.error === "not_found") {
2014-09-25 05:41:07 +08:00
er = new Error("404 Not Found: " + name)
}
else {
2012-06-12 00:30:44 +08:00
er = new Error(
parsed.error + " " + (parsed.reason || "") + ": " + w
)
2012-06-12 00:30:44 +08:00
}
2014-09-25 05:41:07 +08:00
if (name) er.pkgid = name
er.statusCode = response.statusCode
er.code = "E" + er.statusCode
2012-06-12 00:30:44 +08:00
}
return cb(er, parsed, data, response)
}.bind(this)
}