mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
212 lines
6.4 KiB
212 lines
6.4 KiB
module.exports = CachingRegistryClient
|
|
|
|
var path = require('path')
|
|
var fs = require('graceful-fs')
|
|
var url = require('url')
|
|
var assert = require('assert')
|
|
var inherits = require('util').inherits
|
|
|
|
var RegistryClient = require('npm-registry-client')
|
|
var npm = require('../npm.js')
|
|
var log = require('npmlog')
|
|
var getCacheStat = require('./get-stat.js')
|
|
var cacheFile = require('npm-cache-filename')
|
|
var mkdirp = require('mkdirp')
|
|
var rimraf = require('rimraf')
|
|
var chownr = require('chownr')
|
|
var writeFile = require('write-file-atomic')
|
|
var parseJSON = require('../utils/parse-json')
|
|
|
|
function CachingRegistryClient (config) {
|
|
RegistryClient.call(this, adaptConfig(config))
|
|
|
|
this._mapToCache = cacheFile(config.get('cache'))
|
|
|
|
// swizzle in our custom cache invalidation logic
|
|
this._request = this.request
|
|
this.request = this._invalidatingRequest
|
|
this.get = get
|
|
}
|
|
inherits(CachingRegistryClient, RegistryClient)
|
|
|
|
CachingRegistryClient.prototype._invalidatingRequest = function (uri, params, cb) {
|
|
var client = this
|
|
this._request(uri, params, function () {
|
|
var args = arguments
|
|
|
|
var method = params.method
|
|
if (method !== 'HEAD' && method !== 'GET') {
|
|
var invalidated = client._mapToCache(uri)
|
|
// invalidate cache
|
|
//
|
|
// This is irrelevant for commands that do etag / last-modified caching,
|
|
// but ls and view also have a timed cache, so this keeps the user from
|
|
// thinking that it didn't work when it did.
|
|
// Note that failure is an acceptable option here, since the only
|
|
// result will be a stale cache for some helper commands.
|
|
log.verbose('request', 'invalidating', invalidated, 'on', method)
|
|
return rimraf(invalidated, function () {
|
|
cb.apply(undefined, args)
|
|
})
|
|
}
|
|
|
|
cb.apply(undefined, args)
|
|
})
|
|
}
|
|
|
|
function get (uri, params, cb) {
|
|
assert(typeof uri === 'string', 'must pass registry URI to get')
|
|
assert(params && typeof params === 'object', 'must pass params to get')
|
|
assert(typeof cb === 'function', 'must pass callback to get')
|
|
|
|
var parsed = url.parse(uri)
|
|
assert(
|
|
parsed.protocol === 'http:' || parsed.protocol === 'https:',
|
|
'must have a URL that starts with http: or https:'
|
|
)
|
|
|
|
var cacheBase = cacheFile(npm.config.get('cache'))(uri)
|
|
var cachePath = path.join(cacheBase, '.cache.json')
|
|
|
|
// If the GET is part of a write operation (PUT or DELETE), then
|
|
// skip past the cache entirely, but still save the results.
|
|
if (uri.match(/\?write=true$/)) {
|
|
log.verbose('get', 'GET as part of write; not caching result')
|
|
return get_.call(this, uri, cachePath, params, cb)
|
|
}
|
|
|
|
var client = this
|
|
fs.stat(cachePath, function (er, stat) {
|
|
if (!er) {
|
|
fs.readFile(cachePath, function (er, data) {
|
|
data = parseJSON.noExceptions(data)
|
|
|
|
params.stat = stat
|
|
params.data = data
|
|
|
|
get_.call(client, uri, cachePath, params, cb)
|
|
})
|
|
} else {
|
|
get_.call(client, uri, cachePath, params, cb)
|
|
}
|
|
})
|
|
}
|
|
|
|
function get_ (uri, cachePath, params, cb) {
|
|
var staleOk = params.staleOk === undefined ? false : params.staleOk
|
|
var timeout = params.timeout === undefined ? -1 : params.timeout
|
|
var data = params.data
|
|
var stat = params.stat
|
|
var etag
|
|
var lastModified
|
|
|
|
timeout = Math.min(timeout, npm.config.get('cache-max') || 0)
|
|
timeout = Math.max(timeout, npm.config.get('cache-min') || -Infinity)
|
|
if (process.env.COMP_CWORD !== undefined &&
|
|
process.env.COMP_LINE !== undefined &&
|
|
process.env.COMP_POINT !== undefined) {
|
|
timeout = Math.max(timeout, 60000)
|
|
}
|
|
|
|
if (data) {
|
|
if (data._etag) etag = data._etag
|
|
if (data._lastModified) lastModified = data._lastModified
|
|
|
|
if (stat && timeout && timeout > 0) {
|
|
if ((Date.now() - stat.mtime.getTime()) / 1000 < timeout) {
|
|
log.verbose('get', uri, 'not expired, no request')
|
|
delete data._etag
|
|
delete data._lastModified
|
|
return cb(null, data, JSON.stringify(data), { statusCode: 304 })
|
|
}
|
|
|
|
if (staleOk) {
|
|
log.verbose('get', uri, 'staleOk, background update')
|
|
delete data._etag
|
|
delete data._lastModified
|
|
process.nextTick(
|
|
cb.bind(null, null, data, JSON.stringify(data), { statusCode: 304 })
|
|
)
|
|
cb = function () {}
|
|
}
|
|
}
|
|
}
|
|
|
|
var options = {
|
|
etag: etag,
|
|
lastModified: lastModified,
|
|
follow: params.follow,
|
|
auth: params.auth
|
|
}
|
|
this.request(uri, options, function (er, remoteData, raw, response) {
|
|
// if we get an error talking to the registry, but we have it
|
|
// from the cache, then just pretend we got it.
|
|
if (er && cachePath && data && !data.error) {
|
|
er = null
|
|
response = { statusCode: 304 }
|
|
}
|
|
|
|
if (response) {
|
|
log.silly('get', 'cb', [response.statusCode, response.headers])
|
|
if (response.statusCode === 304 && (etag || lastModified)) {
|
|
remoteData = data
|
|
log.verbose(etag ? 'etag' : 'lastModified', uri + ' from cache')
|
|
}
|
|
}
|
|
|
|
data = remoteData
|
|
if (!data) er = er || new Error('failed to fetch from registry: ' + uri)
|
|
|
|
if (er) return cb(er, data, raw, response)
|
|
|
|
saveToCache(cachePath, data, saved)
|
|
|
|
// just give the write the old college try. if it fails, whatever.
|
|
function saved () {
|
|
delete data._etag
|
|
delete data._lastModified
|
|
cb(er, data, raw, response)
|
|
}
|
|
|
|
function saveToCache (cachePath, data, saved) {
|
|
log.verbose('get', 'saving', data.name, 'to', cachePath)
|
|
getCacheStat(function (er, st) {
|
|
mkdirp(path.dirname(cachePath), function (er, made) {
|
|
if (er) return saved()
|
|
|
|
writeFile(cachePath, JSON.stringify(data), function (er) {
|
|
if (er) return saved()
|
|
|
|
chownr(made || cachePath, st.uid, st.gid, saved)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
function adaptConfig (config) {
|
|
return {
|
|
proxy: {
|
|
http: config.get('proxy'),
|
|
https: config.get('https-proxy'),
|
|
localAddress: config.get('local-address')
|
|
},
|
|
ssl: {
|
|
certificate: config.get('cert'),
|
|
key: config.get('key'),
|
|
ca: config.get('ca'),
|
|
strict: config.get('strict-ssl')
|
|
},
|
|
retry: {
|
|
retries: config.get('fetch-retries'),
|
|
factor: config.get('fetch-retry-factor'),
|
|
minTimeout: config.get('fetch-retry-mintimeout'),
|
|
maxTimeout: config.get('fetch-retry-maxtimeout')
|
|
},
|
|
userAgent: config.get('user-agent'),
|
|
log: log,
|
|
defaultTag: config.get('tag'),
|
|
couchToken: config.get('_token')
|
|
}
|
|
}
|
|
|