lookup.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. var path = require('path');
  2. var url = require('url');
  3. var async = require('async');
  4. var request = require('request');
  5. var replay = require('request-replay');
  6. var createError = require('./util/createError');
  7. var Cache = require('./util/Cache');
  8. function lookup(name, callback) {
  9. var data;
  10. var that = this;
  11. var registry = this._config.registry.search;
  12. var total = registry.length;
  13. var index = 0;
  14. // If no registry entries were passed..
  15. if (!total) {
  16. return callback();
  17. }
  18. // Lookup package in series in each registry
  19. // endpoint until we got the data
  20. async.doUntil(function (next) {
  21. var remote = url.parse(registry[index]);
  22. var lookupCache = that._lookupCache[remote.host];
  23. // If force flag is disabled we check the cache
  24. if (!that._config.force) {
  25. lookupCache.get(name, function (err, value) {
  26. data = value;
  27. // Don't proceed with making a request if we got an error,
  28. // a value from the cache or if the offline flag is enabled
  29. if (err || data || that._config.offline) {
  30. return next(err);
  31. }
  32. doRequest.call(that, name, index, function (err, entry) {
  33. if (err || !entry) {
  34. return next(err);
  35. }
  36. data = entry;
  37. // Store in cache
  38. lookupCache.set(name, entry, getMaxAge(entry), next);
  39. });
  40. });
  41. // Otherwise, we totally bypass the cache and
  42. // make only the request
  43. } else {
  44. doRequest.call(that, name, index, function (err, entry) {
  45. if (err || !entry) {
  46. return next(err);
  47. }
  48. data = entry;
  49. // Store in cache
  50. lookupCache.set(name, entry, getMaxAge(entry), next);
  51. });
  52. }
  53. }, function () {
  54. // Until the data is unknown or there's still registries to test
  55. return !!data || ++index === total;
  56. }, function (err) {
  57. // If some of the registry entries failed, error out
  58. if (err) {
  59. return callback(err);
  60. }
  61. callback(null, data);
  62. });
  63. }
  64. function doRequest(name, index, callback) {
  65. var req;
  66. var msg;
  67. var requestUrl = this._config.registry.search[index] + '/packages/' + encodeURIComponent(name);
  68. var remote = url.parse(requestUrl);
  69. var headers = {};
  70. var that = this;
  71. if (this._config.userAgent) {
  72. headers['User-Agent'] = this._config.userAgent;
  73. }
  74. req = replay(request.get(requestUrl, {
  75. proxy: remote.protocol === 'https:' ? this._config.httpsProxy : this._config.proxy,
  76. headers: headers,
  77. ca: this._config.ca.search[index],
  78. strictSSL: this._config.strictSsl,
  79. timeout: this._config.timeout,
  80. json: true
  81. }, function (err, response, body) {
  82. // If there was an internal error (e.g. timeout)
  83. if (err) {
  84. return callback(createError('Request to ' + requestUrl + ' failed: ' + err.message, err.code));
  85. }
  86. // If not found, try next
  87. if (response.statusCode === 404) {
  88. return callback();
  89. }
  90. // Abort if there was an error (range different than 2xx)
  91. if (response.statusCode < 200 || response.statusCode > 299) {
  92. return callback(createError('Request to ' + requestUrl + ' failed with ' + response.statusCode, 'EINVRES'));
  93. }
  94. // Validate response body, since we are expecting a JSON object
  95. // If the server returns an invalid JSON, it's still a string
  96. if (typeof body !== 'object') {
  97. return callback(createError('Response of request to ' + requestUrl + ' is not a valid json', 'EINVRES'));
  98. }
  99. var data;
  100. if (body.url) {
  101. data = {
  102. type: 'alias',
  103. url: body.url
  104. };
  105. }
  106. callback(null, data);
  107. }));
  108. if (this._logger) {
  109. req.on('replay', function (replay) {
  110. msg = 'Request to ' + requestUrl + ' failed with ' + replay.error.code + ', ';
  111. msg += 'retrying in ' + (replay.delay / 1000).toFixed(1) + 's';
  112. that._logger.warn('retry', msg);
  113. });
  114. }
  115. }
  116. function getMaxAge(entry) {
  117. // If type is alias, make it 5 days
  118. if (entry.type === 'alias') {
  119. return 5 * 24 * 60 * 60 * 1000;
  120. }
  121. // Otherwise make it 5 minutes
  122. return 5 * 60 * 60 * 1000;
  123. }
  124. function initCache() {
  125. this._lookupCache = this._cache.lookup || {};
  126. // Generate a cache instance for each registry endpoint
  127. this._config.registry.search.forEach(function (registry) {
  128. var cacheDir;
  129. var host = url.parse(registry).host;
  130. // Skip if there's a cache for the same host
  131. if (this._lookupCache[host]) {
  132. return;
  133. }
  134. if (this._config.cache) {
  135. cacheDir = path.join(this._config.cache, encodeURIComponent(host), 'lookup');
  136. }
  137. this._lookupCache[host] = new Cache(cacheDir, {
  138. max: 250,
  139. // If offline flag is passed, we use stale entries from the cache
  140. useStale: this._config.offline
  141. });
  142. }, this);
  143. }
  144. function clearCache(name, callback) {
  145. var lookupCache = this._lookupCache;
  146. var remotes = Object.keys(lookupCache);
  147. if (typeof name === 'function') {
  148. callback = name;
  149. name = null;
  150. }
  151. if (name) {
  152. async.forEach(remotes, function (remote, next) {
  153. lookupCache[remote].del(name, next);
  154. }, callback);
  155. } else {
  156. async.forEach(remotes, function (remote, next) {
  157. lookupCache[remote].clear(next);
  158. }, callback);
  159. }
  160. }
  161. function resetCache() {
  162. var remote;
  163. for (remote in this._lookupCache) {
  164. this._lookupCache[remote].reset();
  165. }
  166. }
  167. lookup.initCache = initCache;
  168. lookup.clearCache = clearCache;
  169. lookup.resetCache = resetCache;
  170. module.exports = lookup;