221 lines
4.8 KiB
JavaScript
221 lines
4.8 KiB
JavaScript
const path = require('path')
|
|
const http = require('http')
|
|
const https = require('https')
|
|
const followRedirects = require('follow-redirects')
|
|
const qs = require('querystring')
|
|
const zlib = require('zlib')
|
|
const {URL} = require('url')
|
|
|
|
const CentraResponse = require('./CentraResponse.js')
|
|
|
|
const supportedCompressions = ['gzip', 'deflate', 'br']
|
|
|
|
const useRequest = (protocol, maxRedirects) => {
|
|
let httpr
|
|
let httpsr
|
|
if (maxRedirects <= 0) {
|
|
httpr = http.request
|
|
httpsr = https.request
|
|
}
|
|
else {
|
|
httpr = followRedirects.http.request
|
|
httpsr = followRedirects.https.request
|
|
}
|
|
|
|
if (protocol === 'http:') {
|
|
return httpr
|
|
}
|
|
else if (protocol === 'https:') {
|
|
return httpsr
|
|
}
|
|
else throw new Error('Bad URL protocol: ' + protocol)
|
|
}
|
|
|
|
module.exports = class CentraRequest {
|
|
constructor (url, method = 'GET') {
|
|
this.url = typeof url === 'string' ? new URL(url) : url
|
|
this.method = method
|
|
this.data = null
|
|
this.sendDataAs = null
|
|
this.reqHeaders = {}
|
|
this.streamEnabled = false
|
|
this.compressionEnabled = false
|
|
this.timeoutTime = null
|
|
this.coreOptions = {}
|
|
this.maxRedirects = 0
|
|
|
|
this.resOptions = {
|
|
'maxBuffer': 50 * 1000000 // 50 MB
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
followRedirects(n) {
|
|
this.maxRedirects = n
|
|
|
|
return this
|
|
}
|
|
|
|
query (a1, a2) {
|
|
if (typeof a1 === 'object') {
|
|
Object.keys(a1).forEach((queryKey) => {
|
|
this.url.searchParams.append(queryKey, a1[queryKey])
|
|
})
|
|
}
|
|
else this.url.searchParams.append(a1, a2)
|
|
|
|
return this
|
|
}
|
|
|
|
path (relativePath) {
|
|
this.url.pathname = path.join(this.url.pathname, relativePath)
|
|
|
|
return this
|
|
}
|
|
|
|
body (data, sendAs) {
|
|
this.sendDataAs = typeof data === 'object' && !sendAs && !Buffer.isBuffer(data) ? 'json' : (sendAs ? sendAs.toLowerCase() : 'buffer')
|
|
this.data = this.sendDataAs === 'form' ? qs.stringify(data) : (this.sendDataAs === 'json' ? JSON.stringify(data) : data)
|
|
|
|
return this
|
|
}
|
|
|
|
header (a1, a2) {
|
|
if (typeof a1 === 'object') {
|
|
Object.keys(a1).forEach((headerName) => {
|
|
this.reqHeaders[headerName.toLowerCase()] = a1[headerName]
|
|
})
|
|
}
|
|
else this.reqHeaders[a1.toLowerCase()] = a2
|
|
|
|
return this
|
|
}
|
|
|
|
timeout (timeout) {
|
|
this.timeoutTime = timeout
|
|
|
|
return this
|
|
}
|
|
|
|
option (name, value) {
|
|
this.coreOptions[name] = value
|
|
|
|
return this
|
|
}
|
|
|
|
stream () {
|
|
this.streamEnabled = true
|
|
|
|
return this
|
|
}
|
|
|
|
compress () {
|
|
this.compressionEnabled = true
|
|
|
|
if (!this.reqHeaders['accept-encoding']) this.reqHeaders['accept-encoding'] = supportedCompressions.join(', ')
|
|
|
|
return this
|
|
}
|
|
|
|
send () {
|
|
return new Promise((resolve, reject) => {
|
|
if (this.data) {
|
|
if (!this.reqHeaders.hasOwnProperty('content-type')) {
|
|
if (this.sendDataAs === 'json') {
|
|
this.reqHeaders['content-type'] = 'application/json'
|
|
}
|
|
else if (this.sendDataAs === 'form') {
|
|
this.reqHeaders['content-type'] = 'application/x-www-form-urlencoded'
|
|
}
|
|
}
|
|
|
|
if (!this.reqHeaders.hasOwnProperty('content-length')) {
|
|
this.reqHeaders['content-length'] = Buffer.byteLength(this.data)
|
|
}
|
|
}
|
|
|
|
const options = Object.assign({
|
|
'protocol': this.url.protocol,
|
|
'host': this.url.hostname.replace('[', '').replace(']', ''),
|
|
'port': this.url.port,
|
|
'path': this.url.pathname + (this.url.search === null ? '' : this.url.search),
|
|
'method': this.method,
|
|
'headers': this.reqHeaders,
|
|
'maxRedirects': this.maxRedirects
|
|
}, this.coreOptions)
|
|
|
|
let req
|
|
|
|
const resHandler = (res) => {
|
|
let stream = res
|
|
|
|
if (this.compressionEnabled) {
|
|
if (res.headers['content-encoding'] === 'gzip') {
|
|
stream = res.pipe(zlib.createGunzip())
|
|
}
|
|
else if (res.headers['content-encoding'] === 'deflate') {
|
|
stream = res.pipe(zlib.createInflate())
|
|
}
|
|
else if (res.headers['content-encoding'] === 'br') {
|
|
stream = res.pipe(zlib.createBrotliDecompress())
|
|
}
|
|
}
|
|
|
|
let centraRes
|
|
|
|
if (this.streamEnabled) {
|
|
resolve(stream)
|
|
}
|
|
else {
|
|
centraRes = new CentraResponse(res, this.resOptions)
|
|
|
|
stream.on('error', (err) => {
|
|
reject(err)
|
|
})
|
|
|
|
stream.on('aborted', () => {
|
|
reject(new Error('Server aborted request'))
|
|
})
|
|
|
|
stream.on('data', (chunk) => {
|
|
centraRes._addChunk(chunk)
|
|
|
|
if (this.resOptions.maxBuffer !== null && centraRes.body.length > this.resOptions.maxBuffer) {
|
|
stream.destroy()
|
|
|
|
reject('Received a response which was longer than acceptable when buffering. (' + this.body.length + ' bytes)')
|
|
}
|
|
})
|
|
|
|
stream.on('end', () => {
|
|
resolve(centraRes)
|
|
})
|
|
}
|
|
}
|
|
|
|
const request = useRequest(this.url.protocol, this.maxRedirects)
|
|
|
|
req = request(options, resHandler)
|
|
|
|
if (this.timeoutTime) {
|
|
req.setTimeout(this.timeoutTime, () => {
|
|
req.abort()
|
|
|
|
if (!this.streamEnabled) {
|
|
reject(new Error('Timeout reached'))
|
|
}
|
|
})
|
|
}
|
|
|
|
req.on('error', (err) => {
|
|
reject(err)
|
|
})
|
|
|
|
if (this.data) req.write(this.data)
|
|
|
|
req.end()
|
|
})
|
|
}
|
|
}
|