179 lines
4.5 KiB
JavaScript
179 lines
4.5 KiB
JavaScript
import fs from 'fs/promises'
|
|
import path from 'path'
|
|
import http from 'http'
|
|
import stream from 'stream'
|
|
import { URL } from 'url'
|
|
import { defaults } from './helper.mjs'
|
|
|
|
export default function Client(port, opts) {
|
|
this.options = defaults(opts, {})
|
|
this.prefix = `http://localhost:${port}`
|
|
}
|
|
|
|
Client.prototype.customRequest = function(method = 'GET', path, body, options = {}) {
|
|
if (path.slice(0, 4) !== 'http') {
|
|
path = this.prefix + path
|
|
}
|
|
let urlObj = new URL(path)
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const opts = defaults(defaults(options, {
|
|
method: method,
|
|
timeout: 500,
|
|
protocol: urlObj.protocol,
|
|
username: urlObj.username,
|
|
password: urlObj.password,
|
|
host: urlObj.hostname,
|
|
port: Number(urlObj.port),
|
|
path: urlObj.pathname + urlObj.search,
|
|
headers: {},
|
|
}))
|
|
|
|
if (options.agent) {
|
|
opts.agent = options.agent
|
|
}
|
|
|
|
// opts.agent = agent
|
|
|
|
const req = http.request(opts)
|
|
|
|
req.on('error', (err) => {
|
|
reject(err)
|
|
})
|
|
req.on('timeout', function() {
|
|
req.destroy()
|
|
reject(new Error(`Request ${method} ${path} timed out`))
|
|
})
|
|
req.on('response', res => {
|
|
let output = ''
|
|
if (options.toPipe) {
|
|
return stream.pipeline(res, options.toPipe, function(err) {
|
|
if (err) {
|
|
return reject(err)
|
|
}
|
|
resolve()
|
|
})
|
|
} else {
|
|
res.setEncoding('utf8')
|
|
|
|
res.on('data', function (chunk) {
|
|
output += chunk.toString()
|
|
})
|
|
}
|
|
|
|
res.on('end', function () {
|
|
if (options.getRaw) {
|
|
output = {
|
|
status: res.statusCode,
|
|
data: output,
|
|
headers: res.headers,
|
|
}
|
|
} else {
|
|
if (!output) return resolve(null)
|
|
try {
|
|
output = JSON.parse(output)
|
|
} catch (e) {
|
|
return reject(new Error(`${e.message} while decoding: ${output}`))
|
|
}
|
|
}
|
|
if (!options.getRaw && output.status && typeof(output.status) === 'number') {
|
|
let err = new Error(`Request failed [${output.status}]: ${output.message}`)
|
|
err.body = output
|
|
return reject(err)
|
|
}
|
|
resolve(output)
|
|
})
|
|
})
|
|
|
|
if (opts.returnRequest) {
|
|
return resolve(req)
|
|
}
|
|
|
|
if (body) {
|
|
req.write(body)
|
|
}
|
|
req.end()
|
|
return req
|
|
})
|
|
}
|
|
|
|
Client.prototype.get = function(url = '/') {
|
|
return this.customRequest('GET', url, null)
|
|
}
|
|
|
|
Client.prototype.post = function(url = '/', body = {}) {
|
|
let parsed = JSON.stringify(body)
|
|
return this.customRequest('POST', url, parsed, {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Content-Length': parsed.length,
|
|
},
|
|
})
|
|
}
|
|
|
|
Client.prototype.del = function(url = '/', body = {}) {
|
|
return this.customRequest('DELETE', url, JSON.stringify(body))
|
|
}
|
|
|
|
const random = (length = 8) => {
|
|
// Declare all characters
|
|
let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
// Pick characers randomly
|
|
let str = '';
|
|
for (let i = 0; i < length; i++) {
|
|
str += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
}
|
|
return str;
|
|
}
|
|
|
|
Client.prototype.upload = function(url, files, method = 'POST', body = {}, overrideType = null) {
|
|
const boundary = `---------${random(32)}`
|
|
const crlf = '\r\n'
|
|
let upload = files
|
|
|
|
if (typeof(upload) === 'string') {
|
|
upload = {
|
|
file: files
|
|
}
|
|
}
|
|
let keys = Object.keys(upload)
|
|
let uploadBody = []
|
|
|
|
return Promise.all(keys.map(key => {
|
|
let file = upload[key]
|
|
return fs.readFile(file).then(data => {
|
|
const filename = path.basename(file)
|
|
|
|
uploadBody.push(Buffer.from(
|
|
`${crlf}--${boundary}${crlf}`
|
|
+ `Content-Disposition: form-data; name="${key}"; filename="${filename}"`
|
|
+ (overrideType ? crlf + `Content-Type: ${overrideType}`: '')
|
|
+ crlf
|
|
+ crlf
|
|
))
|
|
uploadBody.push(data)
|
|
})
|
|
}))
|
|
.then(() => {
|
|
uploadBody.push(
|
|
Buffer.concat(Object.keys(body).map(function(key) {
|
|
return Buffer.from(''
|
|
+ `${crlf}--${boundary}${crlf}`
|
|
+ `Content-Disposition: form-data; name="${key}"` + crlf + crlf
|
|
+ JSON.stringify(body[key])
|
|
)
|
|
}))
|
|
)
|
|
uploadBody.push(Buffer.from(`${crlf}--${boundary}--`))
|
|
|
|
let multipartBody = Buffer.concat(uploadBody)
|
|
|
|
return this.customRequest(method, url, multipartBody, {
|
|
timeout: 5000,
|
|
headers: {
|
|
'Content-Type': 'multipart/form-data; boundary=' + boundary,
|
|
'Content-Length': multipartBody.length,
|
|
},
|
|
})
|
|
})
|
|
}
|