Skip to content

Instantly share code, notes, and snippets.

@shirish87
Created November 21, 2017 08:56
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shirish87/20878c089b2b589e49be5c0d6b1e889a to your computer and use it in GitHub Desktop.
Save shirish87/20878c089b2b589e49be5c0d6b1e889a to your computer and use it in GitHub Desktop.
Apply response timeout when downloading large files from S3 using Axios.
const axios = require('axios')
const fs = require('fs-extra')
const TimeoutStream = require('./timeoutStream')
const DEFAULT_TIMEOUT = 10000
module.exports.download = function download(downloadURL, destPath, timeout=DEFAULT_TIMEOUT) {
const tmpPath = `${destPath}.tmp`
const timeoutStream = new TimeoutStream({ timeout })
return axios.request({
url: downloadURL,
method: 'GET',
responseType: 'stream',
// timeout // https://github.com/axios/axios/issues/1004
})
.then(res => {
// when server responds too slowly or network temporarily goes away, download stalls forever
// https://github.com/axios/axios/issues/459
return new Promise((resolve, reject) => {
const dest = fs.createWriteStream(tmpPath)
dest.on('error', reject)
res.data.on('error', reject)
.on('end', resolve)
.pipe(timeoutStream)
.on('timeout', err => {
res.data.req.abort()
// trigger a fake response error
res.data.emit('error', err)
})
.pipe(dest)
})
})
.then(() => fs.move(tmpPath, destPath, { overwrite: true }))
.then(() => fs.remove(tmpPath))
.then(() => destPath)
.catch(err => {
timeoutStream.clear()
try {
// attempt tmp file cleanup
fs.removeSync(tmpPath)
} catch (e) {
}
// rethrow original error
throw err
})
}
const Downloader = require('./downloader')
Downloader.download('https://s3-us-west-2.amazonaws.com/<your-large-file>', 'out.file')
.then(path => console.info(`Downloaded to: ${path}`))
.catch(console.error)
const PassThrough = require('stream').PassThrough
module.exports = class TimeoutStream extends PassThrough {
constructor(options={}) {
super()
const { timeout=10000, errorMessage='Timeout!' } = options
this._timeout = timeout
this._errorMessage = errorMessage
this.clear = this.clear.bind(this)
this.on('end', this.clear)
}
_transform(chunk, encoding, callback) {
if (this._timeout > 0) {
// clear existing timer
this.clear()
this._timer = setTimeout(() =>
this.emit('timeout', new Error(this._errorMessage)), this._timeout)
}
callback(null, chunk)
}
clear() {
if (this._timer) {
clearTimeout(this._timer)
this._timer = undefined
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment