tencentcloud-sdk-nodejs-common
Version:
Tencent Cloud API NODEJS SDK
233 lines (214 loc) • 5.52 kB
text/typescript
import * as QueryString from "querystring"
import { URL } from "url"
import * as isStream from "is-stream"
import * as getStream from "get-stream"
import * as FormData from "form-data"
import Sign from "../sign"
import fetch, { FetchOptions } from "./fetch"
import { Response, RequestInit } from "node-fetch"
import { Agent } from "http"
import * as JSONBigInt from "json-bigint"
const JSONbigNative = JSONBigInt({ useNativeBigInt: true })
/**
* @inner
*/
export class HttpConnection {
static async doRequest({
method,
url,
data,
timeout,
headers = {},
agent,
proxy,
signal,
}: {
method: string
url: string
data: any
timeout: number
headers?: Record<string, string>
agent?: Agent
proxy?: string
signal?: AbortSignal
}): Promise<Response> {
const config: FetchOptions = {
method: method,
headers: Object.assign({}, headers),
timeout,
agent,
proxy,
signal,
}
if (method === "GET") {
url += "?" + QueryString.stringify(data)
} else {
config.headers["Content-Type"] = "application/x-www-form-urlencoded"
config.body = QueryString.stringify(data)
}
return await fetch(url, config)
}
static async doRequestWithSign3({
method,
url,
data,
service,
action,
region,
version,
secretId,
secretKey,
multipart = false,
timeout = 60000,
token,
requestClient,
language,
headers = {},
agent,
proxy,
signal,
}: {
method: string
url: string
data: any
service: string
action: string
region: string
version: string
secretId: string
secretKey: string
multipart?: boolean
timeout?: number
token: string
requestClient: string
language: string
headers?: Record<string, string>
agent?: Agent
proxy?: string
signal?: AbortSignal
}): Promise<Response> {
// data 中可能带有 readStream,由于需要计算整个 body 的 hash,
// 所以这里把 readStream 转为 Buffer
// eslint-disable-next-line @typescript-eslint/no-use-before-define
await convertReadStreamToBuffer(data)
// eslint-disable-next-line @typescript-eslint/no-use-before-define
data = deepRemoveNull(data)
const timestamp = parseInt(String(new Date().getTime() / 1000))
method = method.toUpperCase()
let payload = ""
if (method === "GET") {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
data = mergeData(data)
url += "?" + QueryString.stringify(data)
}
if (method === "POST") {
payload = data
}
const config: FetchOptions = {
method,
timeout,
headers: Object.assign({}, headers, {
Host: new URL(url).host,
"X-TC-Action": action,
"X-TC-Region": region,
"X-TC-Timestamp": timestamp,
"X-TC-Version": version,
"X-TC-Token": token,
"X-TC-RequestClient": requestClient,
}),
agent,
proxy,
signal,
}
if (token === null || token === undefined) {
delete config.headers["X-TC-Token"]
}
if (region === null || region === undefined) {
delete config.headers["X-TC-Region"]
}
if (language) {
config.headers["X-TC-Language"] = language
}
let form
if (method === "GET") {
config.headers["Content-Type"] = "application/x-www-form-urlencoded"
}
if (method === "POST" && !multipart) {
config.body = data
const contentType = config.headers["Content-Type"] || "application/json"
if (!isBuffer(data)) config.body = JSONbigNative.stringify(data)
config.headers["Content-Type"] = contentType
}
if (method === "POST" && multipart) {
form = new FormData()
for (const key in data) {
form.append(key, data[key])
}
config.body = form
config.headers = Object.assign({}, config.headers, form.getHeaders())
}
const signature = Sign.sign3({
method,
url,
payload,
timestamp,
service,
secretId,
secretKey,
multipart,
boundary: form ? form.getBoundary() : undefined,
headers: config.headers,
})
config.headers["Authorization"] = signature
return await fetch(url, config)
}
}
async function convertReadStreamToBuffer(data: any): Promise<void> {
for (const key in data) {
if (isStream(data[key])) {
data[key] = await getStream.buffer(data[key])
}
}
}
function mergeData(data: any, prefix = "") {
const ret: any = {}
for (const k in data) {
if (data[k] === null) {
continue
}
if (data[k] instanceof Array || data[k] instanceof Object) {
Object.assign(ret, mergeData(data[k], prefix + k + "."))
} else {
ret[prefix + k] = data[k]
}
}
return ret
}
function deepRemoveNull(obj: any) {
if (isArray(obj)) {
return obj.map(deepRemoveNull)
} else if (isObject(obj)) {
const result: any = {}
for (const key in obj) {
const value = obj[key]
if (!isNull(value)) {
result[key] = deepRemoveNull(value)
}
}
return result
} else {
return obj
}
}
function isBuffer(x: any): boolean {
return Buffer.isBuffer(x)
}
function isArray(x: any): boolean {
return Array.isArray(x)
}
function isObject(x: any): boolean {
return typeof x === "object" && !isArray(x) && !isStream(x) && !isBuffer(x) && x !== null
}
function isNull(x: any): boolean {
return x === null
}