node/deps/npm/node_modules/npm-registry-client/lib/request.js
Kat Marchán d44a9eb11b deps: upgrade npm to 3.10.8
PR-URL: https://github.com/nodejs/node/pull/8706
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
2016-09-27 16:39:27 -04:00

310 lines
9.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

module.exports = regRequest
// npm: means
// 1. https
// 2. send authorization
// 3. content-type is 'application/json' -- metadata
//
var assert = require('assert')
var url = require('url')
var zlib = require('zlib')
var Stream = require('stream').Stream
var STATUS_CODES = require('http').STATUS_CODES
var request = require('request')
var once = require('once')
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)
// Since there are multiple places where an error could occur,
// don't let the cb be called more than once.
var cb = once(cb_)
if (uri.match(/^\/?favicon.ico/)) {
return cb(new Error("favicon.ico isn't a package, it's a picture."))
}
var adduserChange = /\/?-\/user\/org\.couchdb\.user:([^/]+)\/-rev/
var isUserChange = uri.match(adduserChange)
var adduserNew = /\/?-\/user\/org\.couchdb\.user:([^/?]+)$/
var isNewUser = uri.match(adduserNew)
var alwaysAuth = params.auth && params.auth.alwaysAuth
var isDelete = params.method === 'DELETE'
var isWrite = params.body || isDelete
if (isUserChange && !isWrite) {
return cb(new Error('trying to change user document without writing(?!)'))
}
// 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) {
this.log.verbose('request', "new user, so can't send auth")
params.authed = false
} else if (alwaysAuth) {
this.log.verbose('request', 'always-auth set; sending authorization')
params.authed = true
} else if (isWrite) {
this.log.verbose('request', 'sending authorization for write operation')
params.authed = true
} else {
// most of the time we don't want to auth
this.log.verbose('request', 'no auth needed')
params.authed = false
}
var self = this
this.attempt(function (operation) {
makeRequest.call(self, uri, params, function (er, parsed, raw, response) {
if (response) {
self.log.verbose('headers', response.headers)
if (response.headers['npm-notice']) {
self.log.warn('notice', response.headers['npm-notice'])
}
}
if (!er || (er.message && er.message.match(/^SSL Error/))) {
if (er) er.code = 'ESSL'
return cb(er, parsed, raw, response)
}
// Only retry on 408, 5xx or no `response`.
var statusCode = response && response.statusCode
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)
return undefined
}
cb.apply(null, arguments)
})
})
}
function makeRequest (uri, params, cb_) {
var socket
var cb = once(function (er, parsed, raw, response) {
if (socket) {
// The socket might be returned to a pool for re-use, so dont keep
// the 'error' listener from here attached.
socket.removeListener('error', cb)
}
return cb_(er, parsed, raw, response)
})
var parsed = url.parse(uri)
var headers = {}
// metadata should be compressed
headers['accept-encoding'] = 'gzip'
var er = this.authify(params.authed, parsed, headers, params.auth)
if (er) return cb_(er)
var opts = this.initialize(
parsed,
params.method,
'application/json',
headers
)
opts.followRedirect = (typeof params.follow === 'boolean' ? params.follow : true)
opts.encoding = null // tell request let body be Buffer instance
if (params.etag) {
this.log.verbose('etag', params.etag)
headers[params.method === 'GET' ? 'if-none-match' : 'if-match'] = params.etag
}
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
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) {
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
}
}
this.log.http('request', params.method, parsed.href || '/')
var done = requestDone.call(this, params.method, uri, cb)
var req = request(opts, params.streaming ? undefined : decodeResponseBody(done))
req.on('error', cb)
// This should not be necessary, as the HTTP implementation in Node
// passes errors occurring on the socket to the request itself. Being overly
// cautious comes at a low cost, though.
req.on('socket', function (s) {
socket = s
socket.on('error', cb)
})
if (params.streaming) {
req.on('response', function (response) {
if (response.statusCode >= 400) {
var parts = []
response.on('data', function (data) {
parts.push(data)
})
response.on('end', function () {
decodeResponseBody(done)(null, response, Buffer.concat(parts))
})
} else {
response.on('end', function () {
// 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()
}
})
return cb(null, response)
}
})
}
if (params.body && (params.body instanceof Stream)) {
params.body.pipe(req)
}
}
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)
})
}
}
// cb(er, parsed, raw, response)
function requestDone (method, where, cb) {
return function (er, response, data) {
if (er) return cb(er)
var urlObj = url.parse(where)
if (urlObj.auth) urlObj.auth = '***'
this.log.http(response.statusCode, url.format(urlObj))
if (Buffer.isBuffer(data)) {
data = data.toString()
}
var parsed
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) {
var code = response.statusCode
return cb(
makeError(code + ' ' + STATUS_CODES[code], null, code),
null,
data,
response
)
}
er = null
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) {
var w = url.parse(where).pathname.substr(1)
var name
if (!w.match(/^-/)) {
w = w.split('/')
name = decodeURIComponent(w[w.indexOf('_rewrite') + 1])
}
if (!parsed.error) {
er = makeError(
'Registry returned ' + response.statusCode +
' for ' + method +
' on ' + where,
name,
response.statusCode
)
} else if (name && parsed.error === 'not_found') {
er = makeError('404 Not Found: ' + name, name, response.statusCode)
} else {
er = makeError(
parsed.error + ' ' + (parsed.reason || '') + ': ' + (name || w),
name,
response.statusCode
)
}
}
return cb(er, parsed, data, response)
}.bind(this)
}
function makeError (message, name, code) {
var er = new Error(message)
if (name) er.pkgid = name
if (code) {
er.statusCode = code
er.code = 'E' + code
}
return er
}