如何使用Node.js下载文件(不使用第三方库)?

时间:2012-08-14 02:21:45

标签: javascript node.js express download fs

如何在不使用第三方库的情况下下载Node.js 的文件

我不需要任何特别的东西。我只想从给定的URL下载文件,然后将其保存到给定的目录。

31 个答案:

答案 0 :(得分:468)

不要忘记处理错误!以下代码基于Augusto Roman的回答。

var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  var request = http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);  // close() is async, call cb after close completes.
    });
  }).on('error', function(err) { // Handle errors
    fs.unlink(dest); // Delete the file async. (But we don't check the result)
    if (cb) cb(err.message);
  });
};

答案 1 :(得分:467)

您可以创建HTTP GET请求并将其response通过管道传输到可写文件流中:

const http = require('http');
const fs = require('fs');

const file = fs.createWriteStream("file.jpg");
const request = http.get("http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg", function(response) {
  response.pipe(file);
});

如果您想支持在命令行上收集信息 - 比如指定目标文件或目录或URL - 请查看Commander之类的内容。

答案 2 :(得分:123)

正如Brandon Tilley所说,但有适当的控制流程:

var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  var request = http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);
    });
  });
}

无需等待finish事件,天真的脚本可能会以不完整的文件结束。

修改:感谢@Augusto Roman指出应将cb传递给file.close,而不是明确调用。

答案 3 :(得分:57)

说到处理错误,它甚至可以更好地监听请求错误。我甚至通过检查响应代码来验证。这里仅认为200响应代码成功,但其他代码可能是好的。

const fs = require('fs');
const http = require('http');

const download = (url, dest, cb) => {
    const file = fs.createWriteStream(dest);

    const request = http.get(url, (response) => {
        // check if response is success
        if (response.statusCode !== 200) {
            return cb('Response status was ' + response.statusCode);
        }

        response.pipe(file);
    });

    // close() is async, call cb after close completes
    file.on('finish', () => file.close(cb));

    // check for request error too
    request.on('error', (err) => {
        fs.unlink(dest);
        return cb(err.message);
    });

    file.on('error', (err) => { // Handle errors
        fs.unlink(dest); // Delete the file async. (But we don't check the result) 
        return cb(err.message);
    });
};

尽管此代码相对简单,但我建议使用request module,因为它处理了http本身不支持的更多协议(hello HTTPS!)。

这样就可以了:

const fs = require('fs');
const request = require('request');

const download = (url, dest, cb) => {
    const file = fs.createWriteStream(dest);
    const sendReq = request.get(url);

    // verify response code
    sendReq.on('response', (response) => {
        if (response.statusCode !== 200) {
            return cb('Response status was ' + response.statusCode);
        }

        sendReq.pipe(file);
    });

    // close() is async, call cb after close completes
    file.on('finish', () => file.close(cb));

    // check for request errors
    sendReq.on('error', (err) => {
        fs.unlink(dest);
        return cb(err.message);
    });

    file.on('error', (err) => { // Handle errors
        fs.unlink(dest); // Delete the file async. (But we don't check the result)
        return cb(err.message);
    });
};

答案 4 :(得分:42)

gfxmonk的答案在回调和file.close()完成之间的数据竞争非常紧张。 file.close()实际上是在完成关闭时调用的回调。否则,立即使用该文件可能会失败(很少!)。

完整的解决方案是:

var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  var request = http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);  // close() is async, call cb after close completes.
    });
  });
}

无需等待完成事件,天真的脚本可能会以不完整的文件结束。如果不通过close安排cb回调,您可能会在访问文件和实际准备好的文件之间进行竞争。

答案 5 :(得分:13)

超时解决方案,防止内存泄漏:

以下代码基于Brandon Tilley的回答:

var http = require('http'),
    fs = require('fs');

var request = http.get("http://example12345.com/yourfile.html", function(response) {
    if (response.statusCode === 200) {
        var file = fs.createWriteStream("copy.html");
        response.pipe(file);
    }
    // Add timeout.
    request.setTimeout(12000, function () {
        request.abort();
    });
});

当您收到错误时不要创建文件,并且在X借调后优先使用超时来关闭您的请求。

答案 6 :(得分:13)

也许node.js发生了变化,但似乎其他解决方案存在一些问题(使用节点v8.1.2):

  1. 您无需在file.close()活动中致电finish。默认情况下,fs.createWriteStream设置为autoClose:https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
  2. 应该在出错时调用
  3. file.close()。删除文件(unlink())时可能不需要这样做,但通常是:https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  4. statusCode !== 200
  5. 上未删除临时文件 不使用回调的
  6. fs.unlink()已弃用(输出警告)
  7. 如果存在dest文件;它被覆盖了
  8. 以下是处理这些问题的修改后的解决方案(使用ES6和承诺)。

    const http = require("http");
    const fs = require("fs");
    
    function download(url, dest) {
        return new Promise((resolve, reject) => {
            const file = fs.createWriteStream(dest, { flags: "wx" });
    
            const request = http.get(url, response => {
                if (response.statusCode === 200) {
                    response.pipe(file);
                } else {
                    file.close();
                    fs.unlink(dest, () => {}); // Delete temp file
                    reject(`Server responded with ${response.statusCode}: ${response.statusMessage}`);
                }
            });
    
            request.on("error", err => {
                file.close();
                fs.unlink(dest, () => {}); // Delete temp file
                reject(err.message);
            });
    
            file.on("finish", () => {
                resolve();
            });
    
            file.on("error", err => {
                file.close();
    
                if (err.code === "EEXIST") {
                    reject("File already exists");
                } else {
                    fs.unlink(dest, () => {}); // Delete temp file
                    reject(err.message);
                }
            });
        });
    }
    

答案 7 :(得分:13)

对于那些寻找基于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));

答案 8 :(得分:6)

现代版本(ES6、Promise、Node 12.x+)适用于 https/http。它还支持重定向 302 和 301。我决定不使用 3rd 方库,因为它可以通过标准 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

感谢Andrey Tkachenko 为我修改的gist

将其包含在另一个文件中并使用

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()

答案 9 :(得分:5)

✅因此,如果您使用pipeline,它将关闭所有其他流,并确保没有内存泄漏。

工作示例:

const http = require('http');
const { pipeline } = require('stream');
const fs = require('fs');

const file = fs.createWriteStream('./file.jpg');

http.get('http://via.placeholder.com/150/92c952', response => {
  pipeline(
    response,
    file,
    err => {
      if (err)
        console.error('Pipeline failed.', err);
      else
        console.log('Pipeline succeeded.');
    }
  );
});

my answer"What's the difference between .pipe and .pipeline on streams"

答案 10 :(得分:5)

我更喜欢request(),因为您可以同时使用http和https。

request('http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg')
  .pipe(fs.createWriteStream('cat.jpg'))

答案 11 :(得分:4)

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);
        });
    });
}

答案 12 :(得分:4)

const download = (url, path) => new Promise((resolve, reject) => {
http.get(url, response => {
    const statusCode = response.statusCode;

    if (statusCode !== 200) {
        return reject('Download error!');
    }

    const writeStream = fs.createWriteStream(path);
    response.pipe(writeStream);

    writeStream.on('error', () => reject('Error writing to file!'));
    writeStream.on('finish', () => writeStream.close(resolve));
});}).catch(err => console.error(err));

答案 13 :(得分:4)

您可以使用https://github.com/douzi8/ajax-request#download

request.download('http://res.m.ctrip.com/html5/Content/images/57.png', 
  function(err, res, body) {}
);

答案 14 :(得分:3)

基于上述其他答案以及一些细微问题,这是我的尝试。

  1. 仅当获得fs.createWriteStream状态代码时,才创建200 OK。这样可以减少整理临时文件句柄所需的fs.unlink命令的数量。
  2. 即使在200 OK上,由于已经存在reject文件,我们仍然可能EEXIST
  3. 如果您按照标题中提供的链接位置进行download301 Moved Permanently重定向,则递归调用302 Found (Moved Temporarily)
  4. 其他一些答案递归调用download的问题是他们调用了resolve(download)而不是download(...).then(() => resolve()),因此Promise会在下载实际完成之前返回。这样,嵌套的诺言链就可以按正确的顺序解析。
  5. 异步清理临时文件似乎很酷,但是我也选择仅在完成后拒绝,因此我知道当这个承诺解决或拒绝时,一切都开始完成。 / li>
const https = require('https');
const fs = require('fs');

/**
 * Download a resource from `url` to `dest`.
 * @param {string} url - Valid URL to attempt download of resource
 * @param {string} dest - Valid path to save the file.
 * @returns {Promise<void>} - Returns asynchronously when successfully completed download
 */
function download(url, dest) {
  return new Promise((resolve, reject) => {
    const request = https.get(url, response => {
      if (response.statusCode === 200) {
 
        const file = fs.createWriteStream(dest, { flags: 'wx' });
        file.on('finish', () => resolve());
        file.on('error', err => {
          file.close();
          if (err.code === 'EEXIST') reject('File already exists');
          else fs.unlink(dest, () => reject(err.message)); // Delete temp file
        });
        response.pipe(file);
      } else if (response.statusCode === 302 || response.statusCode === 301) {
        //Recursively follow redirects, only a 200 will resolve.
        download(response.headers.location, dest).then(() => resolve());
      } else {
        reject(`Server responded with ${response.statusCode}: ${response.statusMessage}`);
      }
    });

    request.on('error', err => {
      reject(err.message);
    });
  });
}

答案 15 :(得分:2)

使用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);
    }
});

答案 16 :(得分:2)

download.js (即/project/utils/download.js)

#
    # There is insufficient memory for the Java Runtime Environment to continue.
    # Native memory allocation (mmap) failed to map 1073741824 bytes for committing reserved memory.
    # An error report file with more information is saved as:
    # logs/hs_err_pid1.log


app.js

# There is insufficient memory for the Java Runtime Environment to continue.
    # Native memory allocation (mmap) failed to map 1073741824 bytes for committing reserved memory.
    # An error report file with more information is saved as:
    # logs/hs_err_pid1.log

答案 17 :(得分:2)

如果您使用快速使用res.download()方法。否则fs模块使用。

app.get('/read-android', function(req, res) {
   var file = "/home/sony/Documents/docs/Android.apk";
    res.download(file) 
}); 

(或)

   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");
          }
        });  
    }

答案 18 :(得分:1)

写我自己的解决方案,因为现有的解决方案不符合我的要求。

涵盖范围:

  • 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;

答案 19 :(得分:1)

使用http2模块

我看到了使用 http https request 模块的答案。我想使用另一个支持http或https协议的本机NodeJS模块添加一个:

解决方案

我已经参考了官方的NodeJS API,以及针对此问题的其他一些答案。以下是我为进行测试而编写的测试,该测试按预期工作:

awk 'BEGIN { OFS=";"; FS="(^|&)[^&]*=" } { $1=$1; print substr($0,2)";" }'
N;1;0;0;0;

然后,例如:

import * as fs from 'fs';
import * as _path from 'path';
import * as http2 from 'http2';

/* ... */

async download( host, query, destination )
{
    return new Promise
    (
        ( resolve, reject ) =>
        {
            // Connect to client:
            const client = http2.connect( host );
            client.on( 'error', error => reject( error ) );

            // Prepare a write stream:
            const fullPath = _path.join( fs.realPathSync( '.' ), destination );
            const file = fs.createWriteStream( fullPath, { flags: "wx" } );
            file.on( 'error', error => reject( error ) );

            // Create a request:
            const request = client.request( { [':path']: query } );

            // On initial response handle non-success (!== 200) status error:
            request.on
            (
                'response',
                ( headers/*, flags*/ ) =>
                {
                    if( headers[':status'] !== 200 )
                    {
                        file.close();
                        fs.unlink( fullPath, () => {} );
                        reject( new Error( `Server responded with ${headers[':status']}` ) );
                    }
                }
            );

            // Set encoding for the payload:
            request.setEncoding( 'utf8' );

            // Write the payload to file:
            request.on( 'data', chunk => file.write( chunk ) );

            // Handle ending the request
            request.on
            (
                'end',
                () =>
                {
                    file.close();
                    client.close();
                    resolve( { result: true } );
                }
            );

            /* 
                You can use request.setTimeout( 12000, () => {} ) for aborting
                after period of inactivity
            */

            // Fire off [flush] the request:
            request.end();
        }
    );
}

外部参考

答案 20 :(得分:1)

路径:img 类型:jpg 随机的uniqid

    function resim(url) {

    var http = require("http");
    var fs = require("fs");
    var sayi = Math.floor(Math.random()*10000000000);
    var uzanti = ".jpg";
    var file = fs.createWriteStream("img/"+sayi+uzanti);
    var request = http.get(url, function(response) {
  response.pipe(file);
});

        return sayi+uzanti;
}

答案 21 :(得分:0)

function download(url, dest, cb) {

  var request = http.get(url, function (response) {

    const settings = {
      flags: 'w',
      encoding: 'utf8',
      fd: null,
      mode: 0o666,
      autoClose: true
    };

    // response.pipe(fs.createWriteStream(dest, settings));
    var file = fs.createWriteStream(dest, settings);
    response.pipe(file);

    file.on('finish', function () {
      let okMsg = {
        text: `File downloaded successfully`
      }
      cb(okMsg);
      file.end(); 
    });
  }).on('error', function (err) { // Handle errors
    fs.unlink(dest); // Delete the file async. (But we don't check the result)
    let errorMsg = {
      text: `Error in file downloadin: ${err.message}`
    }
    if (cb) cb(errorMsg);
  });
};

答案 22 :(得分:0)

var fs = require('fs'),
    request = require('request');

var download = function(uri, filename, callback){
    request.head(uri, function(err, res, body){
    console.log('content-type:', res.headers['content-type']);
    console.log('content-length:', res.headers['content-length']);
    request(uri).pipe(fs.createWriteStream(filename)).on('close', callback);

    }); 
};   

download('https://www.cryptocompare.com/media/19684/doge.png', 'icons/taskks12.png', function(){
    console.log('done');
});

答案 23 :(得分:0)

嗨,我想您可以使用 child_process 模块和curl命令。

const cp = require('child_process');

let download = async function(uri, filename){
    let command = `curl -o ${filename}  '${uri}'`;
    let result = cp.execSync(command);
};


async function test() {
    await download('http://zhangwenning.top/20181221001417.png', './20181221001417.png')
}

test()

此外,当您要下载大文件,多个文件时,可以使用 cluster 模块使用更多的cpu内核。

答案 24 :(得分:0)

没有图书馆可能只是指出错误。以下是一些:

我的建议:

  • 调用wgetcurl
  • 等系统工具
  • 使用像node-wget-promise这样的工具,它也非常简单易用。 var wget = require('node-wget-promise'); wget('http://nodejs.org/images/logo.svg');

答案 25 :(得分:0)

这是另一种无需第三方依赖性也可以搜索重定向的方法:

        var download = function(url, dest, cb) {
            var file = fs.createWriteStream(dest);
            https.get(url, function(response) {
                if ([301,302].indexOf(response.statusCode) !== -1) {
                    body = [];
                    download(response.headers.location, dest, cb);
                  }
              response.pipe(file);
              file.on('finish', function() {
                file.close(cb);  // close() is async, call cb after close completes.
              });
            });
          }

答案 26 :(得分:0)

我建议您使用res.download,如下所示:

app.get('/download', function(req, res){
  const file = `${__dirname}/folder/abc.csv`;
  res.download(file); // Set disposition and send it.
});

答案 27 :(得分:-1)

您可以尝试将res.redirect用于https文件下载网址,然后下载该文件。

赞:res.redirect('https//static.file.com/file.txt');

答案 28 :(得分:-1)

我发现这种方法最有用,尤其是在处理 pdf 和其他随机文件时。

import fs from "fs";

  fs.appendFile("output_file_name.ext", fileDataInBytes, (err) => {
    if (err) throw err;
    console.log("File saved!");
  });

答案 29 :(得分:-5)

我们可以使用下载节点模块,它非常简单,请参考下面 https://www.npmjs.com/package/download

答案 30 :(得分:-5)

var requestModule=require("request");

requestModule(filePath).pipe(fs.createWriteStream('abc.zip'));