ydr-ali-oss
Version:
ali oss for ydr.me
287 lines (242 loc) • 7.47 kB
JavaScript
/*!
* oss
* @author ydr.me
* @create 2014-11-27 21:47
*/
;
var path = require('path');
var klass = require('ydr-util').class;
var dato = require('ydr-util').dato;
var typeis = require('ydr-util').typeis;
var request = require('ydr-util').request;
var mime = require('ydr-util').mime;
var Busboy = require('busboy');
var crypto = require('crypto');
var xmlParse = require('xml2js').parseString;
var howdo = require('howdo');
var imagesize = require('imagesize');
var REG_META = /^x-oss-meta-/i;
var REG_TITLE = /<title>([\s\S]*?)<\/title>/;
var constructorDefaults = {
accessKeyId: '',
accessKeySecret: '',
bucket: '',
host: 'oss-cn-hangzhou.aliyuncs.com',
domain: '',
onbeforeput: function (streamOptions, next) {
next();
}
};
var putDefaults = {
// 缓存期限 1年
cacheControl: 'max-age=315360000',
// 不需要写 x-oss-meta- 前缀
meta: null
};
/**
* 实例化一个 oss 服务
* @param options
* @param options.accessKeyId {String}
* @param options.accessKeySecret {String}
* @param options.bucket {String}
* @param [options.host] {String}
*/
var OSS = klass.create(function (options) {
this._options = dato.extend(true, {}, constructorDefaults, options);
});
/**
* 重新设置配置
* @param key
* @param val
*/
OSS.fn.setOptions = function (key, val) {
var the = this;
the._options[key] = val;
};
/**
* 中间件传输
* @param req
* @param callback
* @private
*/
OSS.fn._pipe = function (req, callback) {
var busboy = new Busboy({
headers: req.headers
});
var the = this;
// handle files
busboy.on('file', function (fieldName, fileStream, fileName, encoding, contentType) {
if (!fileName) {
return fileStream.resume();
}
var extname = path.extname(fileName);
if (extname) {
contentType = mime.get(extname);
}
var options = {
fieldName: fieldName,
body: fileStream,
fileName: fileName,
encoding: encoding,
contentType: contentType
};
var next = function (err) {
callback(err, options, fileStream);
};
the._options.onbeforeput(options, next);
});
busboy.on('error', callback);
req.pipe(busboy);
};
/**
* 上传文件
* @param req {Object} 请求对象
* @param options {Object} 配置
* @param [options.expires=31536000000] 1年 365*24*60*60*1000,单位ms
* @param [options.cacheControl='public'] 缓存策略
* @param [options.meta=null] meta
* @param [options.object=null] 文件名称
* @param callback
*/
OSS.fn.put = function (req, options, callback) {
var date = new Date();
var the = this;
the._pipe(req, function (err, streamOptions, fileStream) {
if (err) {
return callback(err);
}
options = dato.extend(false, {}, putDefaults, streamOptions, options);
options.encoding = 'utf8';
var headers = {
date: date.toUTCString(),
'content-type': options.contentType,
'content-md5': '',
'cache-control': options.cacheControl
};
the._cleanMeta(options);
dato.each(options.meta, function (key, val) {
headers['x-oss-meta-' + key] = val;
});
options.url = the._createURL(options.object);
the._sign('put', options.object, headers);
options.headers = headers;
howdo
.task(function (done) {
imagesize(options.body, function (err, ret) {
if (err) {
return done(null, {
image: false
});
}
done(null, {
image: {
type: ret.format,
width: ret.width,
height: ret.height
}
});
});
})
.task(function (done) {
request.put(options, function (err, body, res) {
fileStream.resume();
if (err) {
return done(err);
}
if (res.statusCode === 200) {
return done(null, {
contentType: options.contentType,
encoding: options.encoding,
cacheControl: options.cacheControl,
ourl: options.url,
surl: the._options.domain ? 'http://' + the._options.domain + options.object : options.url
});
}
xmlParse(body, function (err, ret) {
var msg = '';
if (err) {
msg = (body.match(REG_TITLE) || ['', ''])[1];
return done(new Error(msg || 'parse upload result error'));
}
msg = ret && ret.Error && ret.Error.Message;
msg = typeis(msg) === 'array' ? msg[0] : msg;
done(new Error(msg));
});
});
})
.together(function (err, retSize, retReq) {
if (err) {
return callback(err);
}
callback(err, dato.extend(retSize, retReq));
});
});
};
/**
* 头信息签名
* @param method
* @param object
* @param headers
* @param headers['conten-type']
* @param headers['conten-length']
* @param headers['date']
* @private
*/
OSS.fn._sign = function (method, object, headers) {
var options = dato.extend(true, {}, this._options, {
method: method,
object: object
});
var auth = 'OSS ' + options.accessKeyId + ':';
var date = options.date || new Date().toUTCString();
var params = [
options.method.toUpperCase(),
headers['content-md5'],
headers['content-type'],
date
];
var resource = '/' + options.bucket + options.object;
var ossHeaders = {};
var signature;
dato.each(headers, function (key, val) {
var lkey = key.toLowerCase().trim();
if (lkey.indexOf('x-oss-') === 0) {
ossHeaders[lkey] = ossHeaders[lkey] || [];
ossHeaders[lkey].push(val.trim());
}
});
Object.keys(ossHeaders).sort().forEach(function (key) {
params.push(key + ':' + ossHeaders[key].join(','));
});
params.push(resource);
signature = crypto.createHmac('sha1', options.accessKeySecret);
signature = signature.update(params.join('\n')).digest('base64');
return dato.extend(true, headers, {
Authorization: auth + signature,
Date: date
});
};
/**
* 清理 meta
* @private
*/
OSS.fn._cleanMeta = function (options) {
var meta = {
user: 'ydr.me'
};
dato.each(options.meta, function (key, val) {
meta[key.replace(REG_META, '')] = val;
});
options.meta = meta;
};
/**
* 生成访问URL
* @param [object]
* @returns {string}
* @private
*/
OSS.fn._createURL = function (object) {
return 'http://' + this._options.bucket + '.' + this._options.host +
(object ? object : '');
};
module.exports = OSS;