使用 NodeJS 下载限速文件的最佳方法是什么?
What is the best way to download a file with a speed limit using NodeJS?
我正在尝试找出使用 NodeJS 的内置 HTTPS 模块限速下载文件的最佳方法。 (在底部有一个完整的 python 实现我正在尝试做的事情。)我已经编写了两个不同的函数,它们似乎都按预期完成了工作。
download1
函数,检查当前秒是否超过速度限制,如果是,则暂停下载并创建一个超时,该超时会在恢复下载的那一秒结束时触发。
download2
但是,不是创建超时,而是创建一个每 1000 毫秒触发一次的间隔,并在下载暂停时恢复下载。
我想知道这两种方法中哪一种更好,或者我是否应该采用完全不同的方法。
函数如下:
export const download1 = (url: string, fileName: string, speedLimitInKb: number) => {
return new Promise((resolve, _reject) => {
https.get(url, res => {
const stream = fs.createWriteStream(fileName);
let totalSize = 0;
let size = 0;
let speedLimit = kbToBytes(speedLimitInKb);
let startDate: number;
let lastSecond = Date.now();
res.pipe(stream);
res.once("resume", () => {
startDate = Date.now();
console.log(`Started at ${new Date(startDate)}`)
})
res.on("data", (chunk) => {
size += chunk.length;
const now = Date.now();
if (now - lastSecond > 1000) {
lastSecond = Date.now();
totalSize += size;
size = 0;
} else if (size >= speedLimit) {
res.pause();
setTimeout(() => res.resume(), 1000 - (now - lastSecond));
}
});
res.on("resume", () => {
lastSecond = Date.now();
totalSize += size;
size = 0;
})
res.on("end", () => {
const elapsed = (Date.now() - startDate) / 1000;
totalSize += size
stream.end();
console.log(`${bytesToMb(totalSize)} mb of data downloaded in ${elapsed} seconds with a speed of ${bytesToKb(totalSize) / elapsed}`)
resolve(undefined);
});
res.on("error", console.log);
})
})
};
export const download2 = (url: string, fileName: string, speedLimitInKb: number) => {
return new Promise((resolve, _reject) => {
https.get(url, res => {
const stream = fs.createWriteStream(fileName);
let totalSize = 0;
let size = 0;
let speedLimit = kbToBytes(speedLimitInKb);
let startDate: number;
res.pipe(stream);
res.once("resume", () => {
startDate = Date.now();
console.log(`Started at ${new Date(startDate)}`)
})
const interval = setInterval(() => {
if (res.isPaused()) {
res.resume();
}
totalSize += size;
size = 0;
}, 1000);
res.on("data", (chunk) => {
size += chunk.length;
if (size >= speedLimit) {
res.pause();
}
});
res.on("end", () => {
clearInterval(interval);
const elapsed = (Date.now() - startDate) / 1000;
totalSize += size
stream.end();
console.log(`${bytesToMb(totalSize)} mb of data downloaded in ${elapsed} seconds with a speed of ${bytesToKb(totalSize) / elapsed}`)
resolve(undefined);
});
res.on("error", console.log);
});
})
}
附加功能:
export const bytesToKb = (bytes: number) => bytes / 1024;
export const kbToMb = (kb: number) => kb / 1024;
export const kbToBytes = (kb: number) => kb * 1024;
export const mbToKb = (mb: number) => mb * 1024;
export const mbToBytes = (mb: number) => mb * 1024 * 1024;
export const bytesToMb = (bytes: number) => bytes / 1024 / 1024;
export const bytesToGb = (bytes: number) => bytes / 1024 / 1024 / 1024;
export const secondsToMs = (seconds: number) => seconds * 1000;
export const msToSeconds = (ms: number) => ms / 1000;
我已经写了一个 Python 版本来实现我想要实现的目标,它适用于任何速度限制和文件大小。我想弄清楚如何在 nodejs 中实现它:
import requests
import time
def download(url, file_name, speed_limit_in_kb):
start = time.time()
size = 0
total_size = 0
with open(file_name, "wb") as f:
with requests.get(url, stream=True) as res:
last_second = time.time()
for part in res.iter_content(1024):
f.write(part)
total_size += len(part)
size += len(part)
offset = time.time() - last_second
if offset > 1:
size = 0
last_second = time.time()
elif size > (1024 * speed_limit_in_kb):
time.sleep(1 - offset)
size = 0
last_second = time.time()
elapsed = time.time() - start
print(f"{total_size / 1024 / 1024} mb of data downloaded in {elapsed} seconds with a speed of {total_size / 1024 / elapsed}")
这类问题肯定会得到自以为是的答案。就个人而言,我会使用 nodejs 的 built-in 流功能来进行节流。使用这种方法的观察结果:
- 最少的代码
- 代码依赖于 (nodejs) 库代码而不是自定义代码
- 高性能。最小的开销,也 wrt。内存
- 缺点:对于那些不熟悉流的人来说,代码似乎很复杂
import fs from "fs";
import https from "https";
import stream from "stream";
import util from "util";
async function downloadWithBackpressure(url, filename, byteRate) {
let totalBytesDownloaded = 0;
const timeBeforeStart = Date.now();
await util.promisify(stream.pipeline)(
// Start the download stream
await new Promise(resolve => https.get(url, resolve)),
// Throttle data by combining setTimeout with a stream.Transform
new stream.Transform({
transform: async (chunk, encoding, next) => {
// Accumulate the total number of bytes received
totalBytesDownloaded += chunk.byteLength;
// Sleep to throttle towards desired transfer speed
const sleepMs = Math.max(0, (totalBytesDownloaded / byteRate * 1000) - Date.now() + timeBeforeStart);
sleepMs && await new Promise(resolve => setTimeout(resolve, sleepMs));
// Propagate the chunk to the stream writable
next(null, chunk);
}
}),
// Save the file to disk
fs.createWriteStream(filename)
);
}
我正在尝试找出使用 NodeJS 的内置 HTTPS 模块限速下载文件的最佳方法。 (在底部有一个完整的 python 实现我正在尝试做的事情。)我已经编写了两个不同的函数,它们似乎都按预期完成了工作。
download1
函数,检查当前秒是否超过速度限制,如果是,则暂停下载并创建一个超时,该超时会在恢复下载的那一秒结束时触发。
download2
但是,不是创建超时,而是创建一个每 1000 毫秒触发一次的间隔,并在下载暂停时恢复下载。
我想知道这两种方法中哪一种更好,或者我是否应该采用完全不同的方法。
函数如下:
export const download1 = (url: string, fileName: string, speedLimitInKb: number) => {
return new Promise((resolve, _reject) => {
https.get(url, res => {
const stream = fs.createWriteStream(fileName);
let totalSize = 0;
let size = 0;
let speedLimit = kbToBytes(speedLimitInKb);
let startDate: number;
let lastSecond = Date.now();
res.pipe(stream);
res.once("resume", () => {
startDate = Date.now();
console.log(`Started at ${new Date(startDate)}`)
})
res.on("data", (chunk) => {
size += chunk.length;
const now = Date.now();
if (now - lastSecond > 1000) {
lastSecond = Date.now();
totalSize += size;
size = 0;
} else if (size >= speedLimit) {
res.pause();
setTimeout(() => res.resume(), 1000 - (now - lastSecond));
}
});
res.on("resume", () => {
lastSecond = Date.now();
totalSize += size;
size = 0;
})
res.on("end", () => {
const elapsed = (Date.now() - startDate) / 1000;
totalSize += size
stream.end();
console.log(`${bytesToMb(totalSize)} mb of data downloaded in ${elapsed} seconds with a speed of ${bytesToKb(totalSize) / elapsed}`)
resolve(undefined);
});
res.on("error", console.log);
})
})
};
export const download2 = (url: string, fileName: string, speedLimitInKb: number) => {
return new Promise((resolve, _reject) => {
https.get(url, res => {
const stream = fs.createWriteStream(fileName);
let totalSize = 0;
let size = 0;
let speedLimit = kbToBytes(speedLimitInKb);
let startDate: number;
res.pipe(stream);
res.once("resume", () => {
startDate = Date.now();
console.log(`Started at ${new Date(startDate)}`)
})
const interval = setInterval(() => {
if (res.isPaused()) {
res.resume();
}
totalSize += size;
size = 0;
}, 1000);
res.on("data", (chunk) => {
size += chunk.length;
if (size >= speedLimit) {
res.pause();
}
});
res.on("end", () => {
clearInterval(interval);
const elapsed = (Date.now() - startDate) / 1000;
totalSize += size
stream.end();
console.log(`${bytesToMb(totalSize)} mb of data downloaded in ${elapsed} seconds with a speed of ${bytesToKb(totalSize) / elapsed}`)
resolve(undefined);
});
res.on("error", console.log);
});
})
}
附加功能:
export const bytesToKb = (bytes: number) => bytes / 1024;
export const kbToMb = (kb: number) => kb / 1024;
export const kbToBytes = (kb: number) => kb * 1024;
export const mbToKb = (mb: number) => mb * 1024;
export const mbToBytes = (mb: number) => mb * 1024 * 1024;
export const bytesToMb = (bytes: number) => bytes / 1024 / 1024;
export const bytesToGb = (bytes: number) => bytes / 1024 / 1024 / 1024;
export const secondsToMs = (seconds: number) => seconds * 1000;
export const msToSeconds = (ms: number) => ms / 1000;
我已经写了一个 Python 版本来实现我想要实现的目标,它适用于任何速度限制和文件大小。我想弄清楚如何在 nodejs 中实现它:
import requests
import time
def download(url, file_name, speed_limit_in_kb):
start = time.time()
size = 0
total_size = 0
with open(file_name, "wb") as f:
with requests.get(url, stream=True) as res:
last_second = time.time()
for part in res.iter_content(1024):
f.write(part)
total_size += len(part)
size += len(part)
offset = time.time() - last_second
if offset > 1:
size = 0
last_second = time.time()
elif size > (1024 * speed_limit_in_kb):
time.sleep(1 - offset)
size = 0
last_second = time.time()
elapsed = time.time() - start
print(f"{total_size / 1024 / 1024} mb of data downloaded in {elapsed} seconds with a speed of {total_size / 1024 / elapsed}")
这类问题肯定会得到自以为是的答案。就个人而言,我会使用 nodejs 的 built-in 流功能来进行节流。使用这种方法的观察结果:
- 最少的代码
- 代码依赖于 (nodejs) 库代码而不是自定义代码
- 高性能。最小的开销,也 wrt。内存
- 缺点:对于那些不熟悉流的人来说,代码似乎很复杂
import fs from "fs";
import https from "https";
import stream from "stream";
import util from "util";
async function downloadWithBackpressure(url, filename, byteRate) {
let totalBytesDownloaded = 0;
const timeBeforeStart = Date.now();
await util.promisify(stream.pipeline)(
// Start the download stream
await new Promise(resolve => https.get(url, resolve)),
// Throttle data by combining setTimeout with a stream.Transform
new stream.Transform({
transform: async (chunk, encoding, next) => {
// Accumulate the total number of bytes received
totalBytesDownloaded += chunk.byteLength;
// Sleep to throttle towards desired transfer speed
const sleepMs = Math.max(0, (totalBytesDownloaded / byteRate * 1000) - Date.now() + timeBeforeStart);
sleepMs && await new Promise(resolve => setTimeout(resolve, sleepMs));
// Propagate the chunk to the stream writable
next(null, chunk);
}
}),
// Save the file to disk
fs.createWriteStream(filename)
);
}