2013-09-27 03:56:13 -05:00
|
|
|
var URL = require('url');
|
2013-06-07 20:16:28 -05:00
|
|
|
var request = require('request');
|
2013-06-14 03:34:29 -05:00
|
|
|
var UError = require('./error').UserError;
|
2013-09-27 03:56:13 -05:00
|
|
|
var mystreams = require('./streams');
|
2013-10-11 00:32:59 -05:00
|
|
|
var Logger = require('./logger');
|
2013-06-07 20:16:28 -05:00
|
|
|
|
2013-09-25 04:12:33 -05:00
|
|
|
//
|
|
|
|
// Implements Storage interface
|
|
|
|
// (same for storage.js, local-storage.js, up-storage.js)
|
|
|
|
//
|
2013-06-19 11:58:16 -05:00
|
|
|
function Storage(config, mainconfig) {
|
2013-06-07 20:16:28 -05:00
|
|
|
if (!(this instanceof Storage)) return new Storage(config);
|
|
|
|
this.config = config;
|
2013-09-28 12:31:58 -05:00
|
|
|
this.is_alive = false;
|
|
|
|
this.userAgent = mainconfig.user_agent;
|
2013-10-01 13:02:23 -05:00
|
|
|
this.ca = config.ca;
|
2013-10-11 00:32:59 -05:00
|
|
|
this.logger = Logger.logger.child({sub: 'out'});
|
2013-06-07 20:16:28 -05:00
|
|
|
|
2013-06-19 11:58:16 -05:00
|
|
|
this.url = URL.parse(this.config.url);
|
|
|
|
if (this.url.hostname === 'registry.npmjs.org') {
|
2013-10-01 13:02:23 -05:00
|
|
|
this.ca = this.ca || require('./npmsslkeys');
|
2013-06-20 08:41:07 -05:00
|
|
|
|
|
|
|
// npm registry is too slow working with ssl :(
|
|
|
|
/*if (this.config._autogenerated) {
|
2013-06-19 11:58:16 -05:00
|
|
|
// encrypt all the things!
|
|
|
|
this.url.protocol = 'https';
|
|
|
|
this.config.url = URL.format(this.url);
|
2013-06-20 08:41:07 -05:00
|
|
|
}*/
|
2013-06-07 20:16:28 -05:00
|
|
|
}
|
2013-06-19 11:58:16 -05:00
|
|
|
|
|
|
|
this.config.url = this.config.url.replace(/\/$/, '');
|
2013-06-07 20:16:28 -05:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2013-09-28 12:31:58 -05:00
|
|
|
Storage.prototype.request = function(options, cb) {
|
|
|
|
var self = this;
|
|
|
|
var headers = options.headers || {};
|
|
|
|
headers.accept = headers.accept || 'application/json';
|
|
|
|
headers['user-agent'] = headers['user-agent'] || this.userAgent;
|
2013-10-11 00:32:59 -05:00
|
|
|
|
|
|
|
var method = options.method || 'GET';
|
|
|
|
var uri = options.uri_full || (this.config.url + options.uri);
|
|
|
|
var method = options.method || 'GET';
|
|
|
|
self.logger.info({
|
|
|
|
method: method,
|
|
|
|
headers: headers,
|
|
|
|
uri: uri,
|
|
|
|
}, "making request: '@{method} @{uri}'");
|
|
|
|
|
|
|
|
if (typeof(options.json) === 'object' && options.json != null) {
|
|
|
|
var json = JSON.stringify(options.json);
|
2013-10-11 04:50:41 -05:00
|
|
|
headers['content-type'] = headers['content-type'] || 'application/json';
|
2013-10-11 00:32:59 -05:00
|
|
|
}
|
|
|
|
|
2013-09-28 12:31:58 -05:00
|
|
|
var req = request({
|
2013-10-11 00:32:59 -05:00
|
|
|
url: uri,
|
|
|
|
method: method,
|
2013-09-28 12:31:58 -05:00
|
|
|
headers: headers,
|
2013-10-11 00:32:59 -05:00
|
|
|
body: json,
|
2013-09-28 12:31:58 -05:00
|
|
|
ca: this.ca,
|
2013-10-11 00:32:59 -05:00
|
|
|
}, function(err, res, body) {
|
2013-10-11 00:46:37 -05:00
|
|
|
if (!err) {
|
|
|
|
var res_length = body.length;
|
|
|
|
|
|
|
|
if (options.json) {
|
|
|
|
try {
|
|
|
|
body = JSON.parse(body);
|
|
|
|
} catch(err) {
|
|
|
|
return cb(err);
|
|
|
|
}
|
2013-10-11 00:32:59 -05:00
|
|
|
}
|
|
|
|
|
2013-10-11 00:46:37 -05:00
|
|
|
if (typeof(body) === 'object' && body !== null) {
|
|
|
|
if (body.error) {
|
|
|
|
var error = body.error;
|
|
|
|
}
|
2013-10-11 00:32:59 -05:00
|
|
|
}
|
2013-10-11 00:46:37 -05:00
|
|
|
} else {
|
|
|
|
var error = err.message;
|
2013-10-11 00:32:59 -05:00
|
|
|
}
|
|
|
|
|
2013-10-11 00:46:37 -05:00
|
|
|
var msg = '@{!status}, req: \'@{request.method} @{request.url}\'';
|
2013-10-11 00:32:59 -05:00
|
|
|
if (error) {
|
|
|
|
msg += ', error: @{!error}';
|
|
|
|
} else {
|
|
|
|
msg += ', bytes: @{bytes.in}/@{bytes.out}';
|
|
|
|
}
|
|
|
|
self.logger.warn({
|
2013-10-11 00:46:37 -05:00
|
|
|
err: err,
|
2013-10-11 00:32:59 -05:00
|
|
|
request: {method: method, url: uri},
|
|
|
|
level: 35, // http
|
2013-10-11 00:46:37 -05:00
|
|
|
status: res != null ? res.statusCode : 'ERR',
|
2013-10-11 00:32:59 -05:00
|
|
|
error: error,
|
|
|
|
bytes: {
|
|
|
|
in: json ? json.length : 0,
|
2013-10-11 00:46:37 -05:00
|
|
|
out: res_length || 0,
|
2013-10-11 00:32:59 -05:00
|
|
|
}
|
|
|
|
}, msg);
|
2013-09-28 12:31:58 -05:00
|
|
|
if (cb) cb.apply(self, arguments);
|
|
|
|
});
|
2013-10-11 00:32:59 -05:00
|
|
|
req.on('response', function(res) {
|
2013-09-28 12:31:58 -05:00
|
|
|
self.status_check(true);
|
|
|
|
});
|
|
|
|
req.on('error', function() {
|
|
|
|
self.status_check(false);
|
|
|
|
});
|
|
|
|
return req;
|
|
|
|
}
|
|
|
|
|
|
|
|
Storage.prototype.status_check = function(alive) {
|
|
|
|
if (arguments.length === 0) {
|
|
|
|
if (!this.is_alive && Math.abs(Date.now() - this.is_alive_time()) > 60*1000) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.is_alive = alive;
|
|
|
|
this.is_alive_time = Date.now();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-19 11:58:16 -05:00
|
|
|
Storage.prototype.can_fetch_url = function(url) {
|
|
|
|
url = URL.parse(url);
|
|
|
|
|
|
|
|
return url.protocol === this.url.protocol
|
|
|
|
&& url.host === this.url.host
|
2013-06-20 08:41:07 -05:00
|
|
|
&& url.path.indexOf(this.url.path) === 0
|
2013-06-19 11:58:16 -05:00
|
|
|
}
|
|
|
|
|
2013-09-25 04:18:38 -05:00
|
|
|
Storage.prototype.add_package = function(name, metadata, callback) {
|
2013-09-28 12:31:58 -05:00
|
|
|
this.request({
|
|
|
|
uri: '/' + escape(name),
|
2013-09-28 05:59:05 -05:00
|
|
|
method: 'PUT',
|
|
|
|
json: metadata,
|
|
|
|
}, function(err, res, body) {
|
|
|
|
if (err) return callback(err);
|
|
|
|
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
|
|
|
|
return callback(new Error('bad status code: ' + res.statusCode));
|
|
|
|
}
|
|
|
|
callback(null, body);
|
|
|
|
});
|
2013-09-25 04:18:38 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
Storage.prototype.add_version = function(name, version, metadata, tag, callback) {
|
2013-09-28 12:31:58 -05:00
|
|
|
this.request({
|
|
|
|
uri: '/' + escape(name) + '/' + escape(version) + '/-tag/' + escape(tag),
|
2013-09-28 06:08:38 -05:00
|
|
|
method: 'PUT',
|
|
|
|
json: metadata,
|
|
|
|
}, function(err, res, body) {
|
|
|
|
if (err) return callback(err);
|
|
|
|
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
|
|
|
|
return callback(new Error('bad status code: ' + res.statusCode));
|
|
|
|
}
|
|
|
|
callback(null, body);
|
|
|
|
});
|
2013-09-25 04:18:38 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
Storage.prototype.add_tarball = function(name, filename) {
|
2013-09-28 07:19:40 -05:00
|
|
|
var stream = new mystreams.UploadTarballStream();
|
|
|
|
var self = this;
|
|
|
|
|
2013-09-28 12:31:58 -05:00
|
|
|
var wstream = this.request({
|
|
|
|
uri: '/' + escape(name) + '/-/' + escape(filename) + '/whatever',
|
2013-09-28 07:19:40 -05:00
|
|
|
method: 'PUT',
|
|
|
|
headers: {
|
|
|
|
'content-type': 'application/octet-stream'
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
wstream.on('response', function(res) {
|
|
|
|
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
|
|
|
|
return stream.emit('error', new UError({
|
|
|
|
msg: 'bad uplink status code: ' + res.statusCode,
|
|
|
|
status: 500,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
stream.emit('success');
|
|
|
|
});
|
|
|
|
|
|
|
|
wstream.on('error', function(err) {
|
|
|
|
stream.emit('error', err);
|
|
|
|
});
|
|
|
|
|
|
|
|
stream.abort = function() {
|
2013-09-28 07:37:24 -05:00
|
|
|
process.nextTick(function() {
|
|
|
|
if (wstream.req) {
|
|
|
|
wstream.req.abort();
|
|
|
|
}
|
|
|
|
});
|
2013-09-28 07:19:40 -05:00
|
|
|
};
|
|
|
|
stream.done = function() {};
|
|
|
|
stream.pipe(wstream);
|
|
|
|
|
|
|
|
return stream;
|
2013-09-25 04:18:38 -05:00
|
|
|
}
|
|
|
|
|
2013-06-07 20:16:28 -05:00
|
|
|
Storage.prototype.get_package = function(name, callback) {
|
2013-09-28 12:31:58 -05:00
|
|
|
this.request({
|
|
|
|
uri: '/' + escape(name),
|
2013-06-07 20:16:28 -05:00
|
|
|
json: true,
|
|
|
|
}, function(err, res, body) {
|
|
|
|
if (err) return callback(err);
|
2013-06-14 03:34:29 -05:00
|
|
|
if (res.statusCode === 404) {
|
|
|
|
return callback(new UError({
|
|
|
|
msg: 'package doesn\'t exist on uplink',
|
|
|
|
status: 404,
|
|
|
|
}));
|
|
|
|
}
|
2013-06-14 02:56:02 -05:00
|
|
|
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
|
|
|
|
return callback(new Error('bad status code: ' + res.statusCode));
|
|
|
|
}
|
2013-06-07 20:16:28 -05:00
|
|
|
callback(null, body);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2013-06-20 08:41:07 -05:00
|
|
|
Storage.prototype.get_tarball = function(name, filename) {
|
|
|
|
return this.get_url(this.config.url + '/' + name + '/-/' + filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
Storage.prototype.get_url = function(url) {
|
2013-09-27 03:56:13 -05:00
|
|
|
var stream = new mystreams.ReadTarballStream();
|
|
|
|
stream.abort = function() {};
|
2013-06-20 08:41:07 -05:00
|
|
|
|
2013-09-28 12:31:58 -05:00
|
|
|
var rstream = this.request({
|
2013-10-02 13:48:32 -05:00
|
|
|
uri_full: url,
|
2013-06-19 11:58:16 -05:00
|
|
|
encoding: null,
|
2013-06-20 08:41:07 -05:00
|
|
|
});
|
2013-10-11 00:32:59 -05:00
|
|
|
|
2013-06-20 08:41:07 -05:00
|
|
|
rstream.on('response', function(res) {
|
2013-06-19 11:58:16 -05:00
|
|
|
if (res.statusCode === 404) {
|
2013-06-20 08:41:07 -05:00
|
|
|
return stream.emit('error', new UError({
|
2013-06-19 11:58:16 -05:00
|
|
|
msg: 'file doesn\'t exist on uplink',
|
|
|
|
status: 404,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
|
2013-06-20 08:41:07 -05:00
|
|
|
return stream.emit('error', new UError({
|
|
|
|
msg: 'bad uplink status code: ' + res.statusCode,
|
|
|
|
status: 500,
|
|
|
|
}));
|
2013-06-19 11:58:16 -05:00
|
|
|
}
|
2013-06-20 08:41:07 -05:00
|
|
|
|
|
|
|
rstream.pipe(stream);
|
|
|
|
});
|
|
|
|
|
|
|
|
rstream.on('error', function(err) {
|
|
|
|
stream.emit('error', err);
|
2013-06-19 11:58:16 -05:00
|
|
|
});
|
2013-06-20 08:41:07 -05:00
|
|
|
return stream;
|
2013-06-19 11:58:16 -05:00
|
|
|
}
|
|
|
|
|
2013-06-07 20:16:28 -05:00
|
|
|
module.exports = Storage;
|
|
|
|
|