mirror of https://github.com/jkjoy/sunpeiwen.git
264 lines
7.6 KiB
JavaScript
264 lines
7.6 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const url = require('url');
|
|
const querystring = require('querystring');
|
|
|
|
const kitx = require('kitx');
|
|
const httpx = require('httpx');
|
|
const xml2js = require('xml2js');
|
|
const JSON = require('json-bigint');
|
|
const debug = require('debug')('roa');
|
|
|
|
const helper = require('./helper');
|
|
|
|
function filter(value) {
|
|
return value.replace(/[\t\n\r\f]/g, ' ');
|
|
}
|
|
|
|
function keyLowerify(headers) {
|
|
const keys = Object.keys(headers);
|
|
const newHeaders = {};
|
|
for (let i = 0; i < keys.length; i++) {
|
|
const key = keys[i];
|
|
newHeaders[key.toLowerCase()] = headers[key];
|
|
}
|
|
return newHeaders;
|
|
}
|
|
|
|
function getCanonicalizedHeaders(headers) {
|
|
const prefix = 'x-acs-';
|
|
const keys = Object.keys(headers);
|
|
|
|
const canonicalizedKeys = [];
|
|
for (let i = 0; i < keys.length; i++) {
|
|
const key = keys[i];
|
|
if (key.startsWith(prefix)) {
|
|
canonicalizedKeys.push(key);
|
|
}
|
|
}
|
|
|
|
canonicalizedKeys.sort();
|
|
|
|
var result = '';
|
|
for (let i = 0; i < canonicalizedKeys.length; i++) {
|
|
const key = canonicalizedKeys[i];
|
|
result += `${key}:${filter(headers[key]).trim()}\n`;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function getCanonicalizedResource(uriPattern, query) {
|
|
const keys = Object.keys(query).sort();
|
|
|
|
if (keys.length === 0) {
|
|
return uriPattern;
|
|
}
|
|
|
|
var result = [];
|
|
for (var i = 0; i < keys.length; i++) {
|
|
const key = keys[i];
|
|
result.push(`${key}=${query[key]}`);
|
|
}
|
|
|
|
return `${uriPattern}?${result.join('&')}`;
|
|
}
|
|
|
|
function buildStringToSign(method, uriPattern, headers, query) {
|
|
const accept = headers['accept'];
|
|
const contentMD5 = headers['content-md5'] || '';
|
|
const contentType = headers['content-type'] || '';
|
|
const date = headers['date'] || '';
|
|
|
|
const header = `${method}\n${accept}\n${contentMD5}\n${contentType}\n${date}\n`;
|
|
|
|
const canonicalizedHeaders = getCanonicalizedHeaders(headers);
|
|
const canonicalizedResource = getCanonicalizedResource(uriPattern, query);
|
|
|
|
return `${header}${canonicalizedHeaders}${canonicalizedResource}`;
|
|
}
|
|
|
|
function parseXML(xml) {
|
|
const parser = new xml2js.Parser({
|
|
// explicitArray: false
|
|
});
|
|
return new Promise((resolve, reject) => {
|
|
parser.parseString(xml, (err, result) => {
|
|
if (err) {
|
|
return reject(err);
|
|
}
|
|
|
|
resolve(result);
|
|
});
|
|
});
|
|
}
|
|
|
|
class ACSError extends Error {
|
|
constructor(err) {
|
|
const message = err.Message[0];
|
|
const code = err.Code[0];
|
|
const hostid = err.HostId[0];
|
|
const requestid = err.RequestId[0];
|
|
super(`${message} hostid: ${hostid}, requestid: ${requestid}`);
|
|
this.code = code;
|
|
}
|
|
}
|
|
|
|
class ROAClient {
|
|
constructor(config) {
|
|
assert(config, 'must pass "config"');
|
|
assert(config.endpoint, 'must pass "config.endpoint"');
|
|
if (!config.endpoint.startsWith('https://') &&
|
|
!config.endpoint.startsWith('http://')) {
|
|
throw new Error(`"config.endpoint" must starts with 'https://' or 'http://'.`);
|
|
}
|
|
assert(config.apiVersion, 'must pass "config.apiVersion"');
|
|
assert(config.accessKeyId, 'must pass "config.accessKeyId"');
|
|
assert(config.accessKeySecret, 'must pass "config.accessKeySecret"');
|
|
|
|
this.endpoint = config.endpoint;
|
|
|
|
this.apiVersion = config.apiVersion;
|
|
this.accessKeyId = config.accessKeyId;
|
|
this.accessKeySecret = config.accessKeySecret;
|
|
this.securityToken = config.securityToken;
|
|
this.host = url.parse(this.endpoint).hostname;
|
|
this.opts = config.opts;
|
|
var httpModule = this.endpoint.startsWith('https://') ? require('https') : require('http');
|
|
this.keepAliveAgent = new httpModule.Agent({
|
|
keepAlive: true,
|
|
keepAliveMsecs: 3000
|
|
});
|
|
}
|
|
|
|
buildHeaders() {
|
|
const now = new Date();
|
|
var defaultHeaders = {
|
|
accept: 'application/json',
|
|
date: now.toGMTString(),
|
|
host: this.host,
|
|
'x-acs-signature-nonce': kitx.makeNonce(),
|
|
'x-acs-signature-method': 'HMAC-SHA1',
|
|
'x-acs-signature-version': '1.0',
|
|
'x-acs-version': this.apiVersion,
|
|
'user-agent': helper.DEFAULT_UA,
|
|
'x-sdk-client': helper.DEFAULT_CLIENT
|
|
};
|
|
if (this.securityToken) {
|
|
defaultHeaders['x-acs-accesskey-id'] = this.accessKeyId;
|
|
defaultHeaders['x-acs-security-token'] = this.securityToken;
|
|
}
|
|
return defaultHeaders;
|
|
}
|
|
|
|
signature(stringToSign) {
|
|
const utf8Buff = Buffer.from(stringToSign, 'utf8');
|
|
|
|
return kitx.sha1(utf8Buff, this.accessKeySecret, 'base64');
|
|
}
|
|
|
|
buildAuthorization(stringToSign) {
|
|
return `acs ${this.accessKeyId}:${this.signature(stringToSign)}`;
|
|
}
|
|
|
|
request(method, uriPattern, query = {}, body = '', headers = {}, opts = {}) {
|
|
var postBody = null;
|
|
|
|
var mixHeaders = Object.assign(this.buildHeaders(), keyLowerify(headers));
|
|
if (body) {
|
|
postBody = Buffer.from(body, 'utf8');
|
|
mixHeaders['content-md5'] = kitx.md5(postBody, 'base64');
|
|
mixHeaders['content-length'] = postBody.length;
|
|
}
|
|
|
|
var url = `${this.endpoint}${uriPattern}`;
|
|
if (Object.keys(query).length) {
|
|
url += `?${querystring.stringify(query)}`;
|
|
}
|
|
|
|
const stringToSign = buildStringToSign(method, uriPattern, mixHeaders, query);
|
|
debug('stringToSign: %s', stringToSign);
|
|
mixHeaders['authorization'] = this.buildAuthorization(stringToSign);
|
|
|
|
const options = Object.assign({
|
|
method,
|
|
agent: this.keepAliveAgent,
|
|
headers: mixHeaders,
|
|
data: postBody
|
|
}, this.opts, opts);
|
|
|
|
return httpx.request(url, options).then((response) => {
|
|
return httpx.read(response, 'utf8').then((body) => {
|
|
// Retrun raw body
|
|
if (opts.rawBody) {
|
|
return body;
|
|
}
|
|
|
|
const contentType = response.headers['content-type'] || '';
|
|
// JSON
|
|
if (contentType.startsWith('application/json')) {
|
|
const statusCode = response.statusCode;
|
|
if (statusCode === 204) {
|
|
return body;
|
|
}
|
|
|
|
var result;
|
|
try {
|
|
result = JSON.parse(body);
|
|
} catch (err) {
|
|
err.name = 'FormatError';
|
|
err.message = 'parse response to json error';
|
|
err.body = body;
|
|
throw err;
|
|
}
|
|
|
|
if (statusCode >= 400) {
|
|
const errorMessage = result.Message || result.errorMsg || '';
|
|
const errorCode = result.Code || result.errorCode || '';
|
|
const requestId = result.RequestId || '';
|
|
var err = new Error(`code: ${statusCode}, ${errorMessage}, requestid: ${requestId}`);
|
|
err.name = `${errorCode}Error`;
|
|
err.statusCode = statusCode;
|
|
err.result = result;
|
|
err.code = errorCode;
|
|
throw err;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
if (contentType.startsWith('text/xml')) {
|
|
return parseXML(body).then((result) => {
|
|
if (result.Error) {
|
|
throw new ACSError(result.Error);
|
|
}
|
|
|
|
return result;
|
|
});
|
|
}
|
|
|
|
return body;
|
|
});
|
|
});
|
|
}
|
|
|
|
put(path, query, body, headers, options) {
|
|
return this.request('PUT', path, query, body, headers, options);
|
|
}
|
|
|
|
post(path, query, body, headers, options) {
|
|
return this.request('POST', path, query, body, headers, options);
|
|
}
|
|
|
|
get(path, query, headers, options) {
|
|
return this.request('GET', path, query, '', headers, options);
|
|
}
|
|
|
|
delete(path, query, headers, options) {
|
|
return this.request('DELETE', path, query, '', headers, options);
|
|
}
|
|
}
|
|
|
|
module.exports = ROAClient;
|