Puede lograr la carga de archivos con Meteor sin usar más paquetes o un tercero
Opción 1:DDP, guardar archivo en una colección mongo
/*** client.js ***/
// asign a change event into input tag
'change input' : function(event,template){
var file = event.target.files[0]; //assuming 1 file only
if (!file) return;
var reader = new FileReader(); //create a reader according to HTML5 File API
reader.onload = function(event){
var buffer = new Uint8Array(reader.result) // convert to binary
Meteor.call('saveFile', buffer);
}
reader.readAsArrayBuffer(file); //read the file as arraybuffer
}
/*** server.js ***/
Files = new Mongo.Collection('files');
Meteor.methods({
'saveFile': function(buffer){
Files.insert({data:buffer})
}
});
Explicación
Primero, el archivo se toma de la entrada usando la API de archivos HTML5. Se crea un lector utilizando el nuevo FileReader. El archivo se lee como readAsArrayBuffer. Este arraybuffer, si console.log, devuelve {} y DDP no puede enviarlo por cable, por lo que debe convertirse a Uint8Array.
Cuando coloca esto en Meteor.call, Meteor automáticamente ejecuta EJSON.stringify(Uint8Array) y lo envía con DDP. Puede verificar los datos en el tráfico websocket de la consola de Chrome, verá una cadena que se parece a base64
En el lado del servidor, Meteor llama a EJSON.parse() y lo vuelve a convertir en búfer
Ventajas
- Simple, sin trucos, sin paquetes adicionales
- Apéguese al principio de datos en el cable
Contras
- Más ancho de banda:la cadena base64 resultante es aproximadamente un 33 % más grande que el archivo original
- Límite de tamaño de archivo:no se pueden enviar archivos grandes (¿límite ~ 16 MB?)
- Sin almacenamiento en caché
- No hay gzip ni compresión todavía
- Usa mucha memoria si publicas archivos
Opción 2:XHR, publicar desde el cliente al sistema de archivos
/*** client.js ***/
// asign a change event into input tag
'change input' : function(event,template){
var file = event.target.files[0];
if (!file) return;
var xhr = new XMLHttpRequest();
xhr.open('POST', '/uploadSomeWhere', true);
xhr.onload = function(event){...}
xhr.send(file);
}
/*** server.js ***/
var fs = Npm.require('fs');
//using interal webapp or iron:router
WebApp.connectHandlers.use('/uploadSomeWhere',function(req,res){
//var start = Date.now()
var file = fs.createWriteStream('/path/to/dir/filename');
file.on('error',function(error){...});
file.on('finish',function(){
res.writeHead(...)
res.end(); //end the respone
//console.log('Finish uploading, time taken: ' + Date.now() - start);
});
req.pipe(file); //pipe the request to the file
});
Explicación
Se captura el archivo en el cliente, se crea un objeto XHR y el archivo se envía a través de 'POST' al servidor.
En el servidor, los datos se canalizan a un sistema de archivos subyacente. Además, puede determinar el nombre del archivo, realizar una desinfección o verificar si ya existe, etc. antes de guardar.
Ventajas
- Aprovechando XHR 2 para que pueda enviar arraybuffer, no se necesita un nuevo FileReader() en comparación con la opción 1
- Arraybuffer es menos voluminoso en comparación con la cadena base64
- Sin límite de tamaño, envié un archivo de ~ 200 MB en localhost sin problema
- El sistema de archivos es más rápido que mongodb (más de esto más adelante en la evaluación comparativa a continuación)
- Caché y gzip
Contras
- XHR 2 no está disponible en navegadores antiguos, p. debajo de IE10, pero, por supuesto, puede implementar una publicación tradicional
- /path/to/dir/ tiene que estar fuera de Meteor, de lo contrario, escribir un archivo en /public desencadena una recarga
Opción 3:XHR, guardar en GridFS
/*** client.js ***/
//same as option 2
/*** version A: server.js ***/
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var GridStore = MongoInternals.NpmModule.GridStore;
WebApp.connectHandlers.use('/uploadSomeWhere',function(req,res){
//var start = Date.now()
var file = new GridStore(db,'filename','w');
file.open(function(error,gs){
file.stream(true); //true will close the file automatically once piping finishes
file.on('error',function(e){...});
file.on('end',function(){
res.end(); //send end respone
//console.log('Finish uploading, time taken: ' + Date.now() - start);
});
req.pipe(file);
});
});
/*** version B: server.js ***/
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var GridStore = Npm.require('mongodb').GridStore; //also need to add Npm.depends({mongodb:'2.0.13'}) in package.js
WebApp.connectHandlers.use('/uploadSomeWhere',function(req,res){
//var start = Date.now()
var file = new GridStore(db,'filename','w').stream(true); //start the stream
file.on('error',function(e){...});
file.on('end',function(){
res.end(); //send end respone
//console.log('Finish uploading, time taken: ' + Date.now() - start);
});
req.pipe(file);
});
Explicación
El script del cliente es el mismo que en la opción 2.
De acuerdo con la última línea de Meteor 1.0.x mongo_driver.js, se expone un objeto global llamado MongoInternals, puede llamar a defaultRemoteCollectionDriver() para devolver el objeto db de la base de datos actual que se requiere para GridStore. En la versión A, GridStore también está expuesto por MongoInternals. El mongo utilizado por el meteorito actual es v1.4.x
Luego, dentro de una ruta, puede crear un nuevo objeto de escritura llamando a var file =new GridStore (...) (API). Luego abre el archivo y crea una transmisión.
También incluí una versión B. En esta versión, GridStore se llama usando una nueva unidad mongodb a través de Npm.require('mongodb'), este mongo es el último v2.0.13 a partir de este escrito. La nueva API no requiere que abra el archivo, puede llamar a stream(true) directamente y comenzar a canalizar
Ventajas
- Igual que en la opción 2, enviado mediante arraybuffer, menos sobrecarga en comparación con la cadena base64 en la opción 1
- No hay necesidad de preocuparse por la desinfección de nombres de archivos
- Separación del sistema de archivos, no es necesario escribir en el directorio temporal, se puede hacer una copia de seguridad de la base de datos, representación, fragmento, etc.
- No es necesario implementar ningún otro paquete
- Se puede guardar en caché y se puede comprimir con gzip
- Almacene tamaños mucho más grandes en comparación con la colección mongo normal
- Uso de tubería para reducir la sobrecarga de memoria
Contras
- Mongo GridFS inestable . Incluí la versión A (mongo 1.x) y B (mongo 2.x). En la versión A, cuando canalizaba archivos grandes> 10 MB, recibí muchos errores, incluido un archivo dañado, canalización sin terminar. Este problema se resuelve en la versión B usando mongo 2.x, con suerte meteor se actualizará a mongodb 2.x pronto
- Confusión de API . En la versión A, debe abrir el archivo antes de poder transmitir, pero en la versión B, puede transmitir sin abrir. El documento API tampoco es muy claro y la transmisión no es 100% intercambiable en sintaxis con Npm.require('fs'). En fs, llama a file.on('finish') pero en GridFS llama a file.on('end') cuando escribe termina/finaliza.
- GridFS no proporciona atomicidad de escritura, por lo que si hay varias escrituras simultáneas en el mismo archivo, el resultado final puede ser muy diferente
- Velocidad . Mongo GridFS es mucho más lento que el sistema de archivos.
Valor de referencia Puede ver en la opción 2 y la opción 3, incluí var start =Date.now() y cuando escribo end, consola.cierro la hora en ms , a continuación se muestra el resultado. Doble núcleo, 4 GB de RAM, HDD, basado en ubuntu 14.04.
file size GridFS FS
100 KB 50 2
1 MB 400 30
10 MB 3500 100
200 MB 80000 1240
Puede ver que FS es mucho más rápido que GridFS. Para un archivo de 200 MB, toma ~80 segundos usando GridFS pero solo ~1 segundo en FS. No he probado SSD, el resultado puede ser diferente. Sin embargo, en la vida real, el ancho de banda puede determinar qué tan rápido se transmite el archivo del cliente al servidor, no es típico lograr una velocidad de transferencia de 200 MB/seg. Por otro lado, una velocidad de transferencia de ~2 MB/seg (GridFS) es más la norma.
Conclusión
De ninguna manera esto es completo, pero puede decidir qué opción es la mejor para sus necesidades.
- DD es el más simple y se adhiere al principio central de Meteor, pero los datos son más voluminosos, no se pueden comprimir durante la transferencia, no se pueden almacenar en caché. Pero esta opción puede ser buena si solo necesita archivos pequeños.
- XHR junto con el sistema de archivos es la forma 'tradicional'. API estable, rápida, 'transmitible', comprimible, almacenable en caché (ETag, etc.), pero debe estar en una carpeta separada
- XHR junto con GridFS , obtiene el beneficio del conjunto de representantes, escalable, sin tocar el directorio del sistema de archivos, archivos grandes y muchos archivos si el sistema de archivos restringe los números, también comprimible en caché. Sin embargo, la API es inestable, se obtienen errores en varias escrituras, es lenta.
Con suerte, pronto, Meteor DDP podrá admitir gzip, almacenamiento en caché, etc., y GridFS podrá ser más rápido. ...