flaska/test/client.mjs
Jonatan Nilsson 499cfa8ce0
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded
FileResponse: Completely finished all HTTP/1.1 support for file handling including range, preconditions, modified and so much more.
Flaska: Refactored how headers and status are sent.
2022-03-26 15:50:18 +00:00

159 lines
4.1 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, file, method = 'POST', body = {}) {
return fs.readFile(file).then(data => {
const crlf = '\r\n'
const filename = path.basename(file)
const boundary = `---------${random(32)}`
const multipartBody = Buffer.concat([
Buffer.from(
`${crlf}--${boundary}${crlf}` +
`Content-Disposition: form-data; name="file"; filename="${filename}"` + crlf + crlf
),
data,
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])
)
})),
Buffer.from(`${crlf}--${boundary}--`),
])
return this.customRequest(method, url, multipartBody, {
timeout: 5000,
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': multipartBody.length,
},
})
})
}