如何在不使用第三方库的情况下使用Node.js下载文件?
我不需要什么特别的东西。我只想从给定的URL下载文件,然后将其保存到给定的目录。
如何在不使用第三方库的情况下使用Node.js下载文件?
我不需要什么特别的东西。我只想从给定的URL下载文件,然后将其保存到给定的目录。
当前回答
编写自己的解决方案,因为现有的不符合我的要求。
包括:
HTTPS下载(http下载时切换包到http) 基于承诺的函数 处理转发路径(状态302) 浏览器头-需要在一些cdn 来自URL的文件名(以及硬编码) 错误处理
打印出来的,更安全。如果你使用的是纯JS(没有Flow,没有TS),可以随意删除类型,或者转换为.d。ts文件
index.js
import httpsDownload from httpsDownload;
httpsDownload('https://example.com/file.zip', './');
httpsDownload.[js|ts]
import https from "https";
import fs from "fs";
import path from "path";
function download(
url: string,
folder?: string,
filename?: string
): Promise<void> {
return new Promise((resolve, reject) => {
const req = https
.request(url, { headers: { "User-Agent": "javascript" } }, (response) => {
if (response.statusCode === 302 && response.headers.location != null) {
download(
buildNextUrl(url, response.headers.location),
folder,
filename
)
.then(resolve)
.catch(reject);
return;
}
const file = fs.createWriteStream(
buildDestinationPath(url, folder, filename)
);
response.pipe(file);
file.on("finish", () => {
file.close();
resolve();
});
})
.on("error", reject);
req.end();
});
}
function buildNextUrl(current: string, next: string) {
const isNextUrlAbsolute = RegExp("^(?:[a-z]+:)?//").test(next);
if (isNextUrlAbsolute) {
return next;
} else {
const currentURL = new URL(current);
const fullHost = `${currentURL.protocol}//${currentURL.hostname}${
currentURL.port ? ":" + currentURL.port : ""
}`;
return `${fullHost}${next}`;
}
}
function buildDestinationPath(url: string, folder?: string, filename?: string) {
return path.join(folder ?? "./", filename ?? generateFilenameFromPath(url));
}
function generateFilenameFromPath(url: string): string {
const urlParts = url.split("/");
return urlParts[urlParts.length - 1] ?? "";
}
export default download;
其他回答
对于那些寻找es6风格的基于承诺的方式的人来说,我猜它会是这样的:
var http = require('http');
var fs = require('fs');
function pDownload(url, dest){
var file = fs.createWriteStream(dest);
return new Promise((resolve, reject) => {
var responseSent = false; // flag to make sure that response is sent only once.
http.get(url, response => {
response.pipe(file);
file.on('finish', () =>{
file.close(() => {
if(responseSent) return;
responseSent = true;
resolve();
});
});
}).on('error', err => {
if(responseSent) return;
responseSent = true;
reject(err);
});
});
}
//example
pDownload(url, fileLocation)
.then( ()=> console.log('downloaded file no issues...'))
.catch( e => console.error('error while downloading', e));
现代版本(ES6, Promise, Node 12。X +)适用于https/http。它还支持重定向302和301。我决定不使用第三方库,因为它可以很容易地使用标准Node.js库。
// download.js
import fs from 'fs'
import https from 'https'
import http from 'http'
import { basename } from 'path'
import { URL } from 'url'
const TIMEOUT = 10000
function download (url, dest) {
const uri = new URL(url)
if (!dest) {
dest = basename(uri.pathname)
}
const pkg = url.toLowerCase().startsWith('https:') ? https : http
return new Promise((resolve, reject) => {
const request = pkg.get(uri.href).on('response', (res) => {
if (res.statusCode === 200) {
const file = fs.createWriteStream(dest, { flags: 'wx' })
res
.on('end', () => {
file.end()
// console.log(`${uri.pathname} downloaded to: ${path}`)
resolve()
})
.on('error', (err) => {
file.destroy()
fs.unlink(dest, () => reject(err))
}).pipe(file)
} else if (res.statusCode === 302 || res.statusCode === 301) {
// Recursively follow redirects, only a 200 will resolve.
download(res.headers.location, dest).then(() => resolve())
} else {
reject(new Error(`Download request failed, response status: ${res.statusCode} ${res.statusMessage}`))
}
})
request.setTimeout(TIMEOUT, function () {
request.abort()
reject(new Error(`Request timeout after ${TIMEOUT / 1000.0}s`))
})
})
}
export default download
感谢安德烈·特卡琴科,我修改了他的要点
将其包含在另一个文件中并使用
const download = require('./download.js')
const url = 'https://raw.githubusercontent.com/replace-this-with-your-remote-file'
console.log('Downloading ' + url)
async function run() {
console.log('Downloading file')
try {
await download(url, 'server')
console.log('Download done')
} catch (e) {
console.log('Download failed')
console.log(e.message)
}
}
run()
Vince Yuan的代码很棒,但似乎有问题。
function download(url, dest, callback) {
var file = fs.createWriteStream(dest);
var request = http.get(url, function (response) {
response.pipe(file);
file.on('finish', function () {
file.close(callback); // close() is async, call callback after close completes.
});
file.on('error', function (err) {
fs.unlink(dest); // Delete the file async. (But we don't check the result)
if (callback)
callback(err.message);
});
});
}
如果您正在使用express,请使用res.download()方法。否则使用fs模块。
app.get('/read-android', function(req, res) {
var file = "/home/sony/Documents/docs/Android.apk";
res.download(file)
});
(or)
function readApp(req,res) {
var file = req.fileName,
filePath = "/home/sony/Documents/docs/";
fs.exists(filePath, function(exists){
if (exists) {
res.writeHead(200, {
"Content-Type": "application/octet-stream",
"Content-Disposition" : "attachment; filename=" + file});
fs.createReadStream(filePath + file).pipe(res);
} else {
res.writeHead(400, {"Content-Type": "text/plain"});
res.end("ERROR File does NOT Exists.ipa");
}
});
}
使用promise下载,它解析可读流。添加额外的逻辑来处理重定向。
var http = require('http');
var promise = require('bluebird');
var url = require('url');
var fs = require('fs');
var assert = require('assert');
function download(option) {
assert(option);
if (typeof option == 'string') {
option = url.parse(option);
}
return new promise(function(resolve, reject) {
var req = http.request(option, function(res) {
if (res.statusCode == 200) {
resolve(res);
} else {
if (res.statusCode === 301 && res.headers.location) {
resolve(download(res.headers.location));
} else {
reject(res.statusCode);
}
}
})
.on('error', function(e) {
reject(e);
})
.end();
});
}
download('http://localhost:8080/redirect')
.then(function(stream) {
try {
var writeStream = fs.createWriteStream('holyhigh.jpg');
stream.pipe(writeStream);
} catch(e) {
console.error(e);
}
});