Descargue un archivo del servidor NodeJS usando Express

318

¿Cómo puedo descargar un archivo que está en mi servidor a mi máquina accediendo a una página en un servidor nodeJS?

Estoy usando el ExpressJS y he estado intentando esto:

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

Pero no puedo obtener el nombre del archivo y el tipo de archivo (o extensión). Alguien me puede ayudar con eso?

Thiago Miranda de Oliveira
fuente
13
Solo para tu información. Para usar en producción, es mejor usar node.js detrás de nginx, y hacer que nginx maneje contenido estático. Aparentemente, es mucho más adecuado para manejar eso.
Munim
3
Los votos
positivos
2
@ user2180794 pero existe tal cosa. Muchas otras preguntas que se marcan y se rechazan son prueba de ello. Sin embargo, esta pregunta ciertamente no es una. Coincide con las pautas :)
Asimilater
La pregunta que usted señala es diferente, aquí OP quiere devolver un archivo a un cliente, mientras que esta otra pregunta es sobre cómo descargar un archivo usando su Nodo de servidor como cliente (por ejemplo, un archivo de un tercero). Al menos eso es lo que entendí.
Eric Burel

Respuestas:

586

Actualizar

Express tiene una ayuda para que esto haga la vida más fácil

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

Vieja respuesta

En lo que respecta a su navegador, el nombre del archivo es solo 'descargar', por lo que debe proporcionarle más información utilizando otro encabezado HTTP.

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

También es posible que desee enviar un tipo MIME como este:

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

Si quieres algo más en profundidad, aquí tienes.

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

Puede establecer el valor del encabezado a lo que quiera. En este caso, estoy usando una biblioteca de tipo mime - node-mime , para verificar cuál es el tipo mime del archivo.

Otra cosa importante a tener en cuenta aquí es que he cambiado su código para usar un readStream. Esta es una manera mucho mejor de hacer las cosas porque usar cualquier método con 'Sincronizar' en el nombre está mal visto porque el nodo está destinado a ser asíncrono.

loganfsmyth
fuente
3
Gracias. ¿Hay alguna forma de obtener esta información de fs.readFileSync? Estoy usando un archivo estático en este ejemplo, pero usaré esta API de descarga para cualquier archivo, pasando el nombre del mismo.
Thiago Miranda de Oliveira
¡Establecer el nombre de archivo de salida funciona con res.setHeader('Content-disposition', 'attachment; filename=' + filename);tnx!
Capy
cómo descargar varios documentos usando el método res.download ().
R J.
1
@RJ. Si tiene una pregunta, cree una nueva, no deje un comentario.
loganfsmyth
77
Express 4.x utiliza en .set()lugar de por .setHeader()cierto
Dana Woodman
48

Utilizar res.download()

Transfiere el archivo en la ruta como un "archivo adjunto". Por ejemplo:

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
fuente
66
¿Qué sucede si los datos provenían de una solicitud HTTP en lugar de un archivo y teníamos que permitir a los usuarios descargar el archivo de forma continua?
summerNight
1
@summerNight: bueno, ese es un caso diferente al de la pregunta especificada. buscar las nodejs proxy file download responsemejores prácticas
Jossef Harush
15

Para archivos estáticos como archivos PDF, documentos de Word, etc. simplemente use la función estática de Express en su configuración:

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

Y luego simplemente coloque todos sus archivos dentro de esa carpeta 'pública', por ejemplo:

/public/docs/my_word_doc.docx

Y luego un enlace antiguo regular permitirá al usuario descargarlo:

<a href="public/docs/my_word_doc.docx">My Word Doc</a>
jordanb
fuente
1
Eso funciona bien para los activos (aunque se recomienda un proxy de servicio dedicado como nginx). Pero para cualquier cosa que requiera acceso seguro, el método aceptado es mejor. En general, para documentos y archivos que contienen información, no recomendaría usar el método público.
nembleton
1
se podría añadir middleware para garantizar que sólo los usuarios apropiados pueden acceder a los archivos
MalcolmOcean
1
por ejemplo this.use('/topsecret', mGetLoggedInUser, mEnsureAccess, express.static('topsecret'))... y luego cada solicitud pasa por mEnsureAccess. Por supuesto, eso significa que tendrá que ser capaz de determinar el nivel de acceso de un usuario solo en función de la URL del documento seguro, o lo que sea.
MalcolmOcean
14

En Express 4.x, hay un 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
fuente
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
fuente
5

Así es como lo hago:

  1. crea un archivo
  2. enviar archivo al cliente
  3. Remover archivo

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
fuente
2
Esta es la única solución que he encontrado en unos dos días de búsqueda que funciona para mi escenario particular de obtener un archivo de audio. lo único es que no creo que res.download () funcione con llamadas $ .ajax desafortunadamente - tuve que usar window.open("/api/get_audio_file");, consulte: stackoverflow.com/a/20177012
user1063287