UNPKG

jshttp

Version:

基于 Axios 的 HTTP 请求库,内置了一些常用功能,支持在任何 Javascript 环境下运行

148 lines (122 loc) 4.59 kB
'use strict' const CryptoJS = require('crypto-js') const getUrl = require('../core/get-url') /** 放在 `config.headers` 中但是非正常请求头字段的字段 */ const axiosExtHeader = ['common', 'delete', 'get', 'head', 'patch', 'post', 'put'] /** * 规格化请求头 * 1. 将字段名变为小写 * 2. 去除重复的字段 */ function normalizeHeaders(headers) { if (typeof headers !== 'object') { return {} } else { return Object.keys(headers).reduce((result, field) => { const lowerField = field.toLowerCase() if (!result[lowerField] && headers[field] && !axiosExtHeader.includes(field)) { result[lowerField] = headers[field] } return result }, {}) } } /** * 获取参与签名的请求头字段 * @param {object} headers 请求头 * @returns {string[]} */ function getSignHeaderKeys(headers) { /** 不参与 Header 签名的请求头 */ const EXCLUDE_SIGN_HEADERS = ['x-ca-signature', 'x-xa-signature-headers', 'accept', 'content-md5', 'content-type', 'date'] const signHeaderKeys = [] Object.keys(headers).forEach((field) => { if (!EXCLUDE_SIGN_HEADERS.includes(field)) { signHeaderKeys.push(field) } }) return signHeaderKeys.sort() } function getSignedHeadersString(signHeaderKeys, headers) { const list = [] for (let i = 0; i < signHeaderKeys.length; i++) { const key = signHeaderKeys[i] const value = headers[key] list.push(key + ':' + (value ? value : '')) } return list.join('\n') } function getPathAndParams(url) { const urlRaw = url.replace('https://', '').replace('http://', '') return urlRaw.substr(urlRaw.indexOf('/')) } function md5(content) { return CryptoJS.MD5(content).toString(CryptoJS.enc.Base64) } function buildStringToSign(method, headers, signedHeadersString, pathAndParams) { const lf = '\n' const list = [method.toUpperCase(), lf] const arr = ['accept', 'content-md5', 'content-type', 'date'] for (let i = 0; i < arr.length; i++) { const key = arr[i] if (headers[key]) { list.push(headers[key]) } list.push(lf) } if (signedHeadersString) { list.push(signedHeadersString) list.push(lf) } if (pathAndParams) { list.push(pathAndParams) } return list.join('') } /** * 阿里云 API 网关签名加密 * @see https://help.aliyun.com/document_detail/29475.html */ module.exports = function aliyunApigwSignatureInterceptor(config) { // 未配置签名参数,则直接跳过 if (typeof config.signature !== 'object') { return config } const { key, secret, debug } = config.signature if (!(key && secret)) { throw new Error('配置了 `signature` 参数但是未配置 `key` 或 `secret`') } const headers = normalizeHeaders(config.headers) // 给请求头添加一些要求添加的字段 headers['x-ca-key'] = key headers['x-ca-timestamp'] = Date.now() headers['accept'] = headers['accept'] || '*/*' headers['content-type'] = headers['content-type'] || 'application/json' // 该请求头要求为一个随机字符串,理论上使用 `UUID` 更好,为了少引入依赖,使用这种方法 headers['x-ca-nonce'] = CryptoJS.MD5(Date.now().toString() + Math.random() * 10000).toString(CryptoJS.enc.Hex) /** 允许包含 `body` 的请求方法 */ const bodyValidMethod = ['post', 'put', 'patch'] if (bodyValidMethod.includes(config.method.toLowerCase())) { if (config.data) { headers['content-md5'] = md5(JSON.stringify(config.data)) } else { console.warn(`当前请求方法为 \`${config.method}\` 但是未配置请求数据(\`data\`)`) } } else { if (config.data) { console.warn('如果要提交请求数据 `data`,请使用以下请求方法:`post`, `put`, `patch` ') } } const signHeaderKeys = getSignHeaderKeys(headers) headers['x-ca-signature-headers'] = signHeaderKeys.join(',') const url = getUrl(config.baseURL, config.url, config.params) const pathAndParams = getPathAndParams(url) const signedHeadersString = getSignedHeadersString(signHeaderKeys, headers) const stringToSign = buildStringToSign(config.method, headers, signedHeadersString, pathAndParams) headers['x-ca-signature'] = CryptoJS.HmacSHA256(stringToSign, secret).toString(CryptoJS.enc.Base64) if (debug) { console.info(`当前签名字符串:\`${stringToSign.replace(/\n/g, '#')}\``) } config.headers = headers return config }