UNPKG

nxkit

Version:

This is a collection of tools, independent of any other libraries

564 lines (563 loc) 19 kB
"use strict"; /* ***** BEGIN LICENSE BLOCK ***** * Distributed under the BSD license: * * Copyright (c) 2015, xuewen.chu * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of xuewen.chu nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL xuewen.chu BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * ***** END LICENSE BLOCK ***** */ Object.defineProperty(exports, "__esModule", { value: true }); const util_1 = require("./util"); const buffer_1 = require("./buffer"); const path_1 = require("./path"); const errno_1 = require("./errno"); const { haveNgui, haveNode, haveWeb } = util_1.default; const _user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) \ AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36'; if (haveNgui) { var user_agent = _user_agent; var httpNgui = __requireNgui__('_http'); } else if (haveWeb) { var user_agent = navigator.userAgent; var XMLHttpRequest = globalThis.XMLHttpRequest; } else if (haveNode) { var user_agent = _user_agent; var http = require('http'); var https = require('https'); } else { throw Error.new('Unimplementation'); } var shared = null; var __id = 1; const defaultOptions = { method: 'GET', params: null, headers: {}, dataType: 'json', userAgent: user_agent, timeout: 18e4, }; function stringifyPrimitive(v) { if (typeof v === 'string') return v; if (typeof v === 'number' && isFinite(v)) return '' + v; if (typeof v === 'boolean') return v ? 'true' : 'false'; return ''; } function querystringStringify(obj, sep = '&', eq = '=') { var encode = encodeURIComponent; if (obj !== null && typeof obj === 'object') { var keys = Object.keys(obj); var len = keys.length; var flast = len - 1; var fields = ''; for (var i = 0; i < len; ++i) { var k = keys[i]; var v = obj[k]; var ks = encode(stringifyPrimitive(k)) + eq; if (Array.isArray(v)) { var vlen = v.length; var vlast = vlen - 1; for (var j = 0; j < vlen; ++j) { fields += ks + encode(stringifyPrimitive(v[j])); if (j < vlast) fields += sep; } if (vlen && i < flast) fields += sep; } else { fields += ks + encode(stringifyPrimitive(v)); if (i < flast) fields += sep; } } return fields; } return ''; } exports.querystringStringify = querystringStringify; function stringifyXml(obj) { var result = ['<xml>']; for (var [k, v] of Object.entries(obj)) { result.push(`<${k}>`); if (v && typeof v == 'object') { result.push(`![CDATA[${v}]]`); } else { result.push(String(v)); } result.push(`</${k}>`); } result.push('</xml>'); return result.join(''); } exports.stringifyXml = stringifyXml; // Ngui implementation function requestNgui(options, soptions, resolve, reject, is_https, method, post_data) { var url = is_https ? 'https://' : 'http://'; url += soptions.hostname; url += soptions.port != (is_https ? 443 : 80) ? ':' + soptions.port : ''; url += soptions.path; httpNgui.request({ url: url, method: method == 'POST' ? http.HTTP_METHOD_POST : http.HTTP_METHOD_GET, headers: soptions.headers, postData: post_data, timeout: soptions.timeout, disableCache: true, disableSslVerify: true, }).then((res) => { resolve({ data: res.data, headers: res.responseHeaders, statusCode: res.statusCode, httpVersion: res.httpVersion, requestHeaders: soptions.headers, requestData: options.params, }); }).catch((err) => { reject(Error.new(err)); }); } function requestWeb(options, soptions, resolve, reject, is_https, method, post_data) { var url = is_https ? 'https://' : 'http://'; url += soptions.hostname; url += soptions.port != (is_https ? 443 : 80) ? ':' + soptions.port : ''; url += soptions.path; url += `${soptions.path.indexOf('?') == -1 ? '?' : '&'}_=${__id++}`; var xhr = new XMLHttpRequest(); xhr.open(method || 'POST', url, true); xhr.responseType = 'arraybuffer'; // xhr.responseType = 'text'; xhr.timeout = soptions.timeout; delete soptions.headers['User-Agent']; for (var key in soptions.headers) { xhr.setRequestHeader(key, soptions.headers[key]); } function parseResponseHeaders(str) { var r = {}; for (var s of str.split(/\r?\n/)) { var index = s.indexOf(':'); if (index != -1) r[s.substr(0, index)] = s.substr(index + 1); } return r; } xhr.onload = async () => { var data = xhr.response; var r = { headers: parseResponseHeaders(xhr.getAllResponseHeaders()), statusCode: xhr.status, httpVersion: '1.1', requestHeaders: soptions.headers, requestData: options.params, }; if (data instanceof ArrayBuffer) { resolve(Object.assign(r, { data: buffer_1.default.from(data) })); } else if (data instanceof Blob && data.arrayBuffer) { data.arrayBuffer().then((e) => resolve(Object.assign(r, { data: buffer_1.default.from(e) }))); } else { resolve(Object.assign(r, { data })); } }; xhr.onerror = (e) => { reject(Error.new(e.message)); }; xhr.ontimeout = () => { reject(Error.new(errno_1.default.ERR_HTTP_REQUEST_TIMEOUT)); }; xhr.send(post_data); } // Node implementation function requestNode(options, soptions, resolve, reject, is_https, method, post_data) { var lib = is_https ? https : http; if (is_https) { soptions.agent = new https.Agent(soptions); } if (method == 'POST') { soptions.headers['Content-Length'] = post_data ? buffer_1.default.byteLength(post_data) : 0; } var req = lib.request(soptions, (res) => { // console.log(`STATUS: ${res.statusCode}`); // console.log(`HEADERS: ${JSON.stringify(res.headers)}`); // res.setEncoding('utf8'); var buffers = []; res.on('data', (chunk) => { // console.log(`BODY: ${chunk}`); buffers.push(buffer_1.default.from(chunk)); }); res.on('end', () => { // console.log('No more data in response.'); // console.log('---requestNode', data + ''); resolve({ data: buffer_1.default.concat(buffers), headers: res.headers, statusCode: res.statusCode, httpVersion: res.httpVersion, requestHeaders: soptions.headers, requestData: options.params, }); }); }); req.on('abort', () => reject(Error.new(errno_1.default.ERR_HTTP_REQUEST_ABORT))); req.on('error', (e) => reject(Error.new(e))); req.on('timeout', () => { reject(Error.new(errno_1.default.ERR_HTTP_REQUEST_TIMEOUT)); req.abort(); }); // write data to request body if (method == 'POST') { if (post_data) req.write(post_data); } req.end(); } // request implementation var _request_platform = haveNgui ? requestNgui : haveWeb ? requestWeb : haveNode ? requestNode : util_1.default.unrealized; /** * @func request */ function request(pathname, opts) { var options = Object.assign({}, defaultOptions, opts); var { params, method, headers = {}, signer } = options; if (util_1.default.config.moreLog) { var data = []; if (params) { if (method == 'GET') { pathname += '?' + querystringStringify(params); } else { data = [`-d '${JSON.stringify(params)}'`]; } } var logs = [ "'" + pathname + "'", '-X ' + method, ...(method == 'POST' ? ["-H 'Content-Type: application/json'"] : []), ...(Object.entries(headers).map(([k, v]) => `-H '${k}: ${v}'`)), ...data, ]; console.log('curl', logs.join(' \\\n')); } return util_1.default.promise(async (resolve, reject) => { var uri = new path_1.default.URL(pathname); var is_https = uri.protocol == 'https:'; var hostname = uri.hostname; var port = Number(uri.port) || (is_https ? 443 : 80); var path = uri.path; uri.clearHash(); var headers = { 'User-Agent': options.userAgent, 'Accept': 'application/json', }; Object.assign(headers, options.headers); var post_data = null; if (method == 'POST') { if (options.urlencoded || options.dataType == 'urlencoded') { headers['Content-Type'] = 'application/x-www-form-urlencoded'; if (params) { post_data = querystringStringify(params); } } else if (options.dataType == 'xml') { headers['Content-Type'] = 'application/xml'; if (params) { if (typeof params == 'string') { post_data = params; } else { post_data = stringifyXml(params); } } } else { headers['Content-Type'] = 'application/json'; if (params) { post_data = JSON.stringify(params); } } } else { if (params) { uri.params = Object.assign(uri.params, params); } } var path = uri.path; if (signer) { Object.assign(headers, await signer.sign(path, post_data)); } if (!haveWeb) { // set proxy var proxy = process.env.HTTP_PROXY || process.env.http_proxy; if (proxy && /^https?:\/\//.test(proxy)) { var proxyUrl = new path_1.default.URL(proxy); is_https = proxyUrl.protocol == 'https:'; hostname = proxyUrl.hostname; port = Number(proxyUrl.port) || (is_https ? 443 : 80); path = uri.href; // set headers headers.host = uri.hostname; if (uri.port) { headers.host += ':' + uri.port; } } } var timeout = Number(options.timeout || ''); var send_options = { hostname, port, path, method, headers, rejectUnauthorized: false, timeout: timeout > -1 ? timeout : defaultOptions.timeout, }; _request_platform(options, send_options, resolve, reject, is_https, method, post_data); }); } exports.request = request; /** * @class Cache */ class Cache { constructor() { this.m_getscache = {}; } has(key) { return key in this.m_getscache; } get(key) { return this.m_getscache[key]; } set(key, data, cacheTiem) { var i = this.m_getscache[key]; if (i) { var id = i.timeoutid; if (id) { clearTimeout(id); } } this.m_getscache[key] = { data: data, time: cacheTiem, timeoutid: cacheTiem ? setTimeout(e => { delete this.m_getscache[key]; }, cacheTiem) : 0, }; } static hash(object) { return util_1.default.hash(JSON.stringify(object)); } } function parseJSON(json) { var res = JSON.parse(json, function (key, value) { // 2019-05-09T00:00:00.000Z if (typeof value == 'string' && value[10] == 'T' && value[23] == 'Z') { return new Date(value); } return value; }); return res; } exports.parseJSON = parseJSON; /** * @class Request */ class Request { constructor(serverURL, mock, mockSwitch) { this.m_data_type = 'urlencoded'; this.m_cache = new Cache(); this.m_timeout = defaultOptions.timeout; this.m_user_agent = user_agent; this.m_server_url = serverURL || util_1.default.config.web_service; this.m_mock = mock || {}; this.m_mock_switch = mockSwitch || null; if (!this.m_server_url) { if (haveWeb) { this.m_server_url = location.origin; } else { this.m_server_url = 'http://localhost'; } } } get userAgent() { return this.m_user_agent; } set userAgent(v) { this.m_user_agent = v; } get urlencoded() { return this.m_data_type == 'urlencoded'; } set urlencoded(v) { this.m_data_type = v ? 'urlencoded' : 'json'; } get dataType() { return this.m_data_type; } set dataType(v) { this.m_data_type = v; } get serverURL() { return this.m_server_url; } set serverURL(v) { this.m_server_url = v; } get mock() { return this.m_mock; } set mock(v) { this.m_mock = v; } get mockSwitch() { return this.m_mock_switch; } set mockSwitch(v) { this.m_mock_switch = v; } get timeout() { return this.m_timeout; } set timeout(value) { this.m_timeout = value; } get signer() { return this.m_signer || null; } set signer(value) { this.m_signer = value ? value : undefined; } /** * @func getRequestHeaders */ getRequestHeaders() { return {}; } parseResponseData(buf) { return buf; } async request(name, method = 'GET', params, options) { var opts = options || {}; var { headers } = opts; var url = this.m_server_url + '/' + name; headers = Object.assign({}, this.getRequestHeaders(), headers); params = params || opts.params; if (this.m_mock[name] && (!this.m_mock_switch || this.m_mock_switch[name])) { return { data: Object.create(this.m_mock[name]), headers: {}, statusCode: 200, httpVersion: '1.1', requestHeaders: headers, requestData: params, }; } else { var result; try { result = await request(url, { method, headers: headers, params: params, timeout: opts.timeout || this.m_timeout, dataType: opts.dataType || this.m_data_type, userAgent: opts.userAgent || this.m_user_agent, signer: opts.signer || this.m_signer, }); } catch (err) { err = Error.new(errno_1.default.ERR_HTTP_REQUEST_FAIL, err); err.url = url; err.requestHeaders = headers; err.requestData = params; throw err; } try { result.data = this.parseResponseData(result.data); return result; } catch (err) { err.url = url; err.headers = result.headers; err.statusCode = result.statusCode; err.httpVersion = result.httpVersion; err.description = result.data.toString('utf-8'); err.requestHeaders = headers; err.requestData = params; throw err; } } } async get(name, params, options) { var { cacheTime } = options || {}; var key = Cache.hash({ name: name, params: params }); var cache = this.m_cache.get(key); if (cacheTime) { if (cache) { return Object.assign({}, cache.data, { cached: true }); } var data = await this.request(name, 'GET', params, options); this.m_cache.set(key, data, cacheTime); return data; } else { var data = await this.request(name, 'GET', params, options); if (cache) { this.m_cache.set(key, data, cache.time); } return data; } } post(name, params, options) { return this.request(name, 'POST', params, options); } call(name, params, options) { if (params) { return this.post(name, params, options); } else { return this.get(name, params, options); } } } exports.Request = Request; exports.default = { Request: Request, /** * @get userAgent */ get userAgent() { return user_agent; }, /** * @func setShared() */ setShared(req) { shared = req; }, /** * @get shared # default web server */ get shared() { return shared; }, /** * @func request() */ request: request, /** * @func get() */ get(url, options = {}) { return request(url, Object.assign({}, options, { method: 'GET' })); }, /** * @func post() */ post(url, options = {}) { return request(url, Object.assign({}, options, { method: 'POST' })); }, };