NodeJS module for downloading a (http) URI.
/* similar functionality to `simple-get`: https://github.com/feross/simple-get/pull/41 */
const fs = require('fs')
const path = require('path')
const urlParser = require('url').parse
const client = {
'http:': require('http'),
'https:': require('https')
}
// const decompressResponse = require('decompress-response') // npm lib
function HttpStatusError(message, removeFile) {
this.message = message
this.removeFile = removeFile
this.stack = new Error().stack
}
HttpStatusError.prototype = Object.create(Error.prototype)
HttpStatusError.prototype.constructor = HttpStatusError
/* inspired by `mkdirp.sync`: https://github.com/substack/node-mkdirp/blob/master/index.js#L55 */
const _0777 = parseInt('0777', 8) & (~process.umask())
function mkdirs(dirs) {
try {
fs.mkdirSync(dirs, _0777)
} catch (err0) {
if (err0.code === 'EEXIST') return
if (err0.code === 'ENOENT') {
mkdirs(path.dirname(dirs)) // chop off last dir, mkdir
mkdirs(dirs) // all dirs, mkdir
return
}
// sanity check
try {
if (!(fs.statSync(dirs).isDirectory())) throw err0
} catch (ignore) {
throw err0
}
}
}
async function httpGet(
url /* : string */,
fileDir /* : string */,
fileName /* : string */,
timeoutMs = (8 * 1000) /* : number */) /* : Promise<function> */ =>
{
const { protocol } = urlParser(url) // validate url early on
mkdirs(fileDir)
const filePath = path.join(fileDir, fileName)
if (fs.existsSync(filePath)) {
throw new Error(`ERROR: Cannot download, file already exists: ${filePath}`)
}
const fileWriteStream = fs.createWriteStream(filePath)
const removeFile = (closeStream = true) => { // this is what gets resolved/rejected
closeStream && fs.closeSync(fs.openSync(filePath, 'r')) // helpful steps for Windows
fs.unlinkSync(filePath)
}
return new Promise((resolve, reject) => {
let statusOK = false
const handleFileClose = () => {
if (statusOK) resolve(removeFile)
else reject(new HttpStatusError('HTTP response: error status', removeFile))
}
const req = client[protocol]
.get(url, response => {
// response = decompressResponse(response)
response.pipe(fileWriteStream)
fileWriteStream.on('finish', () => fileWriteStream.close(handleFileClose)) // close() is async, calls cb after close completes
})
.on('response', incomingMessage => {
statusOK = statusOK || (incomingMessage.statusCode < 400)
})
.on('error', err => {
reject(new HttpStatusError(err.message, removeFile))
})
req.setTimeout(timeoutMs, () => reject(new HttpStatusError('HTTP response: timeout', removeFile)))
req.end()
})
}
httpGet.HttpStatusError = HttpStatusError
httpGet.mkdirs = mkdirs
module.exports = httpGet