Faça o download de um arquivo do NodeJS Server usando o Express

318

Como posso baixar um arquivo que está no meu servidor para minha máquina acessando uma página em um servidor nodeJS?

Estou usando o ExpressJS e tentei isso:

app.get('/download', function(req, res){

  var file = fs.readFileSync(__dirname + '/upload-folder/dramaticpenguin.MOV', 'binary');

  res.setHeader('Content-Length', file.length);
  res.write(file, 'binary');
  res.end();
});

Mas não consigo obter o nome e o tipo (ou extensão) do arquivo. Alguém pode me ajudar com isso?

Thiago Miranda de Oliveira
fonte
13
Apenas para sua informação. Para usar na produção, é melhor usar o node.js por trás do nginx e fazer com que o nginx lide com o conteúdo estático. Aparentemente, é muito mais adequado para lidar com isso.
Munim
3
As votações
positivas
2
@ user2180794 mas existe uma coisa dessas. Muitas outras perguntas que são sinalizadas e rejeitadas são a prova disso. Esta questão certamente não é uma. Ele corresponde às diretrizes :)
Assimilater 8/16
A pergunta que você aponta é diferente, aqui o OP deseja retornar um arquivo para um cliente, enquanto esta outra pergunta é sobre como baixar um arquivo usando o Nó do servidor como cliente (por exemplo, um arquivo de terceiros). Pelo menos é o que eu entendi.
Eric Burel

Respostas:

586

Atualizar

O Express tem um ajudante para facilitar a vida.

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

Resposta antiga

No que diz respeito ao seu navegador, o nome do arquivo é apenas 'download'; portanto, você precisa fornecer mais informações usando outro cabeçalho HTTP.

res.setHeader('Content-disposition', 'attachment; filename=dramaticpenguin.MOV');

Você também pode enviar um tipo MIME como este:

res.setHeader('Content-type', 'video/quicktime');

Se você quer algo mais aprofundado, aqui está.

var path = require('path');
var mime = require('mime');
var fs = require('fs');

app.get('/download', function(req, res){

  var file = __dirname + '/upload-folder/dramaticpenguin.MOV';

  var filename = path.basename(file);
  var mimetype = mime.lookup(file);

  res.setHeader('Content-disposition', 'attachment; filename=' + filename);
  res.setHeader('Content-type', mimetype);

  var filestream = fs.createReadStream(file);
  filestream.pipe(res);
});

Você pode definir o valor do cabeçalho como desejar. Nesse caso, estou usando uma biblioteca do tipo mime - node-mime , para verificar qual é o tipo mime do arquivo.

Outra coisa importante a ser observada aqui é que alterei seu código para usar um readStream. Essa é uma maneira muito melhor de fazer as coisas, porque o uso de qualquer método com 'Sync' no nome é desaprovado, porque o nó deve ser assíncrono.

loganfsmyth
fonte
3
Obrigado .. Existe uma maneira de obter essas informações do fs.readFileSync? Estou usando um arquivo estático neste exemplo, mas usarei esta API de download para todos os arquivos, passando o nome dele.
Thiago Miranda de Oliveira
A configuração do nome do arquivo de saída funciona com o res.setHeader('Content-disposition', 'attachment; filename=' + filename);tnx!
Capy
como baixar vários documentos usando o método res.download ().
R J.
1
@RJ. Se você tiver uma pergunta, crie uma nova, não deixe um comentário.
loganfsmyth
7
Express 4.x usos .set()em vez de .setHeader()btw
Dana Woodman
48

Usar res.download()

Ele transfere o arquivo no caminho como um "anexo". Por exemplo:

var express = require('express');
var router = express.Router();

// ...

router.get('/:id/download', function (req, res, next) {
    var filePath = "/my/file/path/..."; // Or format the path using the `id` rest param
    var fileName = "report.pdf"; // The default name the browser will use

    res.download(filePath, fileName);    
});
Jossef Harush
fonte
6
E se os dados chegassem de uma solicitação HTTP em vez de um arquivo e tivéssemos que permitir que os usuários fizessem o download do arquivo em um fluxo contínuo?
precisa saber é o seguinte
1
@summerNight - bem, esse é um caso diferente da pergunta especificada. procurar nodejs proxy file download responsemelhores práticas
Jossef Harush 20/17
15

Para arquivos estáticos como pdfs, documentos do Word, etc., basta usar a função estática do Express em sua configuração:

// Express config
var app = express().configure(function () {
    this.use('/public', express.static('public')); // <-- This right here
});

E então basta colocar todos os seus arquivos nessa pasta 'pública', por exemplo:

/public/docs/my_word_doc.docx

E, em seguida, um link antigo comum permitirá ao usuário fazer o download:

<a href="public/docs/my_word_doc.docx">My Word Doc</a>
jordanb
fonte
1
Isso funciona bem para ativos (embora seja recomendado um proxy de veiculação dedicado como o nginx). Mas para qualquer coisa que exija acesso seguro, o método aceito é melhor. De um modo geral, para documentos e arquivos que contêm informações, eu não recomendaria o uso do método público.
nembleton
1
você pode adicionar middleware para garantir que apenas usuários apropriados podem acessar os arquivos
MalcolmOcean
1
por exemplo this.use('/topsecret', mGetLoggedInUser, mEnsureAccess, express.static('topsecret'))... e então cada solicitação passa pelo mEnsureAccess. Obviamente, isso significa que você precisará descobrir o nível de acesso de um usuário apenas com base na URL do documento seguro ou qualquer outra coisa.
MalcolmOcean
14

No Express 4.x, existe um attachment()método para Response:

res.attachment();
// Content-Disposition: attachment

res.attachment('path/to/logo.png');
// Content-Disposition: attachment; filename="logo.png"
// Content-Type: image/png
Benoit Blanchon
fonte
6
'use strict';

var express = require('express');
var fs = require('fs');
var compress = require('compression');
var bodyParser = require('body-parser');

var app = express();
app.set('port', 9999);
app.use(bodyParser.json({ limit: '1mb' }));
app.use(compress());

app.use(function (req, res, next) {
    req.setTimeout(3600000)
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept,' + Object.keys(req.headers).join());

    if (req.method === 'OPTIONS') {
        res.write(':)');
        res.end();
    } else next();
});

function readApp(req,res) {
  var file = req.originalUrl == "/read-android" ? "Android.apk" : "Ios.ipa",
      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");
      }
    });  
}

app.get('/read-android', function(req, res) {
    var u = {"originalUrl":req.originalUrl};
    readApp(u,res) 
});

app.get('/read-ios', function(req, res) {
    var u = {"originalUrl":req.originalUrl};
    readApp(u,res) 
});

var server = app.listen(app.get('port'), function() {
    console.log('Express server listening on port ' + server.address().port);
});
KARTHIKEYAN.A
fonte
5

Aqui está como eu faço:

  1. criar arquivo
  2. enviar arquivo para o cliente
  3. remover arquivo

Código:

let fs = require('fs');
let path = require('path');

let myController = (req, res) => {
  let filename = 'myFile.ext';
  let absPath = path.join(__dirname, '/my_files/', filename);
  let relPath = path.join('./my_files', filename); // path relative to server root

  fs.writeFile(relPath, 'File content', (err) => {
    if (err) {
      console.log(err);
    }
    res.download(absPath, (err) => {
      if (err) {
        console.log(err);
      }
      fs.unlink(relPath, (err) => {
        if (err) {
          console.log(err);
        }
        console.log('FILE [' + filename + '] REMOVED!');
      });
    });
  });
};
Vedran
fonte
2
Esta é a única solução que encontrei em cerca de dois dias de pesquisa que funciona no meu cenário específico de obtenção de um arquivo de áudio. a única coisa é que eu não acho que res.download () trabalha com $ .ajax chamadas infelizmente - Eu tive que usar window.open("/api/get_audio_file");, consulte: stackoverflow.com/a/20177012
user1063287