mongodb - subir - Meteor: carga del archivo del cliente a la colección Mongo vs sistema de archivos vs GridFS
subir imagenes a mongodb (2)
Meteor es genial, pero carece de soporte nativo para la carga tradicional de archivos. Hay varias opciones para manejar la carga de archivos:
Desde el cliente , los datos se pueden enviar usando:
- Meteor.call (''saveFile'', data) o collection.insert ({file: data})
- Formulario ''POST'' o HTTP.call (''POST'')
En el servidor , el archivo se puede guardar en:
- una colección de archivos mongodb de collection.insert ({file: data})
- sistema de archivos en / ruta / a / dir
- mongodb GridFS
¿Cuáles son los pros y los contras de estos métodos y la mejor manera de implementarlos? Soy consciente de que también hay otras opciones, como guardar en un sitio de un tercero y obtener una url.
Hola solo para agregar a Option1 con respecto a la visualización del archivo. Lo hice sin ejson.
<template name=''tryUpload''>
<p>Choose file to upload</p>
<input name="upload" class=''fileupload'' type=''file''>
</template>
Template.tryUpload.events({
''change .fileupload'':function(event,template){
console.log(''change & view'');
var f = event.target.files[0];//assuming upload 1 file only
if(!f) return;
var r = new FileReader();
r.onload=function(event){
var buffer = new Uint8Array(r.result);//convert to binary
for (var i = 0, strLen = r.length; i < strLen; i++){
buffer[i] = r.charCodeAt(i);
}
var toString = String.fromCharCode.apply(null, buffer );
console.log(toString);
//Meteor.call(''saveFiles'',buffer);
}
r.readAsArrayBuffer(f);};
Puede lograr la carga de archivos simplemente con Meteor sin usar más paquetes o un tercero
Opción 1: DDP, guardar el 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 HTML5 File API. Se crea un lector usando el nuevo FileReader. El archivo se lee como readAsArrayBuffer. Este arraybuffer, si console.log, devuelve {} y DDP no puede enviarlo a través del cable, por lo que debe convertirse a Uint8Array.
Cuando pones esto en Meteor.call, Meteor ejecuta automáticamente EJSON.stringify (Uint8Array) y lo envía con DDP. Puede verificar los datos en el tráfico de websocket de la consola Chrome, verá una cadena que se asemeja a base64
En el lado del servidor, Meteor llama a EJSON.parse () y lo convierte de nuevo en buffer
Pros
- Simple, sin forma de hacky, sin paquetes adicionales
- Cumpla con el principio de Datos sobre el cable
Contras
- Más ancho de banda: la cadena base64 resultante es ~ 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é
- Sin gzip o compresión aún
- Tome mucha memoria si publica archivos
Opción 2: XHR, publicación 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 toma el archivo del cliente, se crea un objeto XHR y el archivo se envía por ''POST'' al servidor.
En el servidor, los datos se canalizan a un sistema de archivos subyacente. También puede determinar el nombre del archivo, realizar la desinfección o verificar si ya existe, etc. antes de guardar.
Pros
- Aprovechando XHR 2 para poder enviar un 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 ~ 200 MB en localhost sin problemas
- 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)
- Cachable y gzip
Contras
- XHR 2 no está disponible en navegadores más antiguos, por ejemplo, debajo de IE10, pero por supuesto puede implementar una publicación tradicional <form> I only use xhr = new XMLHttpRequest (), en lugar de HTTP.call (''POST'') porque el HTTP actual. llamada en Meteor todavía no puede enviar arraybuffer (señalarme si estoy equivocado).
- / path / to / dir / tiene que estar fuera del meteorito, de lo contrario escribir un archivo en / public dispara 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.
Según 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 base de datos actual que se requiere para GridStore. En la versión A, GridStore también está expuesto por MongoInternals. El mongo usado por el meteoro actual es v1.4.x
Luego, dentro de una ruta, puede crear un nuevo objeto de escritura llamando var file = new GridStore (...) ( API ). A continuación, abre el archivo y crea una secuencia.
También incluí una versión B. En esta versión, se llama a la GridStore utilizando una nueva unidad mongodb a través de Npm.require (''mongodb''), este mongo es el último v2.0.13 al momento de escribir esto. La nueva API no requiere que abras el archivo, puedes invocar stream (true) directamente y comenzar la canalización
Pros
- Lo mismo que en la opción 2, enviado utilizando arraybuffer, menos sobrecarga en comparación con la cadena base64 en la opción 1
- No hay necesidad de preocuparse por la sanitización del nombre de archivo
- 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, rep, fragmento, etc.
- No es necesario implementar ningún otro paquete
- Cachable y puede ser comprimido
- Almacene tamaños mucho más grandes en comparación con la colección mongo normal
- Usar tubería para reducir la sobrecarga de memoria
Contras
- Inestable Mongo GridFS . Incluí la versión A (mongo 1.x) y B (mongo 2.x). En la versión A, cuando canalizo archivos de gran tamaño> 10 MB, recibí muchos errores, incluido un archivo dañado, un conducto 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 llamar. El API doc tampoco es muy claro y la transmisión no es 100% sintaxis intercambiable con Npm.require (''fs''). En fs, llama a file.on (''finalizar'') pero en GridFS llama a file.on (''end'') cuando la escritura 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.
Benchmark Puede ver en la opción 2 y la opción 3, incluí var start = Date.now () y al escribir end, configuro .log el tiempo en ms , a continuación se muestra el resultado. Dual Core, 4 GB ram, HDD, ubuntu 14.04 basado.
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 dictar qué tan rápido se transmite el archivo de cliente a servidor, logrando una velocidad de transferencia de 200 MB / s no es típico. Por otro lado, una velocidad de transferencia ~ 2 MB / s (GridFS) es más la norma.
Conclusión
De ninguna manera esto es completo, pero puede decidir qué opción es mejor para su necesidad.
- El DDP es el más simple y se adhiere al principio Meteor central, pero los datos son más voluminosos, no se pueden comprimir durante la transferencia, no se pueden almacenar en el caché. Pero esta opción puede ser buena si solo necesitas archivos pequeños.
- XHR junto con el sistema de archivos es la forma ''tradicional''. API estable, rápida, ''streamable'', compresible, cachable (ETag, etc.), pero debe estar en una carpeta separada
- XHR junto con GridFS , obtendrá el beneficio de un conjunto de repeticiones, 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 puede comprimirse. Sin embargo, la API es inestable, obtienes errores en varias escrituras, es s..l..o ... w ..
Esperemos que pronto, meteor DDP pueda soportar gzip, almacenamiento en caché, etc. y GridFS puede ser más rápido ...