@lipingruan/node-unionpay-sdk
Version:
nodejs unionpay sdk
297 lines (193 loc) • 6.85 kB
JavaScript
/**
* 常用IO方法封装
* request 支持非UTF-8编码
* stream <-> buffer 互转
*/
const Stream = require('stream');
const http = require ( 'http' );
const https = require ( 'https' );
const querystring = require ( 'querystring' );
const os = require ( 'os' );
const crypto = require ( 'crypto' );
const path = require ( 'path' );
const fs = require ( 'fs' );
/**
* 写入临时文件, 返回文件地址
* @param {Buffer|string} content
* @param {string?} extension
* @returns {string}
*/
exports.writeTmpFile = ( content, extension = 'tmp' ) => {
const xPath = os.tmpdir ( )
const md5 = crypto.createHash ( 'md5' ).update ( content ).digest ( 'hex' )
const filename = [ md5, extension ].join ( '.' )
const savePath = path.join ( xPath, filename )
if ( fs.existsSync ( savePath ) ) return savePath
fs.writeFileSync ( savePath, content )
return savePath
}
/**
* 生成临时文件地址
* @param {string?} extension
* @returns {string}
*/
exports.getTmpFile = ( extension ) => {
const xPath = os.tmpdir ( )
const pid = process.pid.toString( 16 ).padStart ( 4, '0' )
const time = Date.now ( ).toString ( 16 ).padStart ( 11, '0' )
const rnd = String ( Math.floor ( Math.random ( ) * 1000 ) ).padStart ( 3, '0' )
let filename = [ pid, time, rnd ].join ( '_' )
if ( extension ) filename += '.' + extension
return path.join ( xPath, filename )
}
/**
*
* @param {Stream} stream
* @param {Number} totalLength
* @returns {Promise<Buffer>}
*/
exports.stream2buffer = function ( stream, totalLength=0 ) {
return new Promise ( function ( resolve, reject ) {
const buffers = [ ];
stream.on ( 'error', reject );
if ( totalLength ) {
stream.on ( 'data', function ( data ) { buffers.push ( data ) } );
} else {
stream.on ( 'data', function ( data ) { buffers.push ( data ); totalLength += data.length; } )
}
stream.on ( 'end', function ( ) { resolve ( Buffer.concat ( buffers, totalLength ) ) } )
} );
}
/**
*
* @param {Buffer|String} buffer
* @returns {Stream}
*/
exports.buffer2stream = function ( buffer ) {
const stream = new Stream.Duplex ( );
stream.push ( buffer );
stream.push ( null );
return stream;
}
/**
* 解析请求post body
* @param {http.IncomingMessage} request
* @returns {Object|String}
*/
exports.parseBody = async function ( request, responseType ) {
if ( request.method === 'GET' ) return null;
const contentSize = request.headers [ 'content-length' ]
const buffer = await exports.stream2buffer ( request, contentSize ? +contentSize : 0 )
if ( contentSize && buffer.length !== +contentSize )
throw new Error ( 'Incorrect Content-Length' )
if ( request.headers [ 'content-disposition' ] ) return buffer
let contentType = request.headers [ 'content-type' ]
let contentTypeUnwrapped = ''
let charset = 'utf8'
if ( responseType ) {
switch ( responseType ) {
case 'json':
contentTypeUnwrapped = 'application/json'
break;
case 'form':
contentTypeUnwrapped = 'application/x-www-form-urlencoded'
break;
}
}
if ( !contentTypeUnwrapped && contentType ) {
// application/json; charset=utf-8
const s = contentType.split ( ';' )
contentTypeUnwrapped = s [ 0 ].trim ( )
let index = 0, size = s.length
while ( ++index < size ) {
const b = s [ index ].trim ( )
if ( b.startsWith ( 'charset=' ) ) {
charset = b.substr ( 8 ).toLowerCase ( )
break
}
}
}
let string = ''
if ( charset !== 'utf8' && charset !== 'utf-8' ) {
const decoder = new TextDecoder ( charset )
string = decoder.decode ( buffer )
} else string = buffer.toString ( )
try {
switch ( contentTypeUnwrapped ) {
case 'text/plain':
if ( !string.startsWith ( '{' ) && !string.startsWith ( '[' ) ) break
case 'application/json':
return JSON.parse ( string )
case 'application/x-www-form-urlencoded':
return querystring.parse ( string )
default:
return string
}
} catch ( error ) {
return string
}
}
/**
* http(s) request
* @param {http.RequestOptions|https.RequestOptions|URL|String} options
* @param {String|URL} options.url
* @param {*} body
* @param {'json'|'form'|'text'} bodyType
* @param {'json'|'form'|'text'} responseType
* @returns {Promise<{body:*,headers:http.IncomingHttpHeaders}>}
*/
exports.http = function ( options, body, bodyType = 'json', responseType ) {
return new Promise ( function ( resolve, reject ) {
/**
*
* @param {http.IncomingMessage} response
*/
const callback = async function ( response ) {
const headers = response.headers
const bodyLength = headers [ 'content-length' ]
if ( bodyLength && +bodyLength > 0 ) {
try {
resolve ( {
body: await exports.parseBody ( response, responseType ),
headers
} );
} catch ( error ) {
reject ( error );
}
} else resolve ( { headers } )
};
const isHTTPS =
'string' === typeof options ?
options.startsWith ( 'https' ) :
'object' === typeof options ?
options.url ?
'string' === typeof options.url ? options.url.startsWith ( 'https' ) : options.url.protocol === 'https:' :
options.protocol === 'https:'
: false;
const request = isHTTPS ?
options.url ? https.request ( options.url, options, callback ) : https.request ( options, callback ) :
options.url ? http.request ( options.url, options, callback ) : http.request ( options, callback );
if ( body ) {
let bodyString = ''
switch ( bodyType ) {
case 'form':
request.setHeader ( 'Content-Type', 'application/x-www-form-urlencoded' )
bodyString = querystring.stringify ( body )
break
case 'json':
request.setHeader ( 'Content-Type', 'application/json' )
bodyString = JSON.stringify ( body )
break
default:
request.setHeader ( 'Content-Type', 'text/plain' )
bodyString = String ( body )
break
}
request.setHeader ( 'Content-Length', Buffer.byteLength ( bodyString ) );
request.method = 'POST';
request.write ( bodyString );
}
request.end ( );
request.on ( 'error', reject );
} )
}