Anda dapat mencapai pengunggahan file dengan Meteor tanpa menggunakan paket atau pihak ketiga lagi
Opsi 1:DDP, menyimpan file ke koleksi 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})
}
});
Penjelasan
Pertama, file diambil dari input menggunakan HTML5 File API. Pembaca dibuat menggunakan FileReader baru. File dibaca sebagai readAsArrayBuffer. Arraybuffer ini, jika Anda console.log, mengembalikan {} dan DDP tidak bisa mengirim ini melalui kabel, sehingga harus dikonversi ke Uint8Array.
Saat Anda memasukkan ini ke Meteor.call, Meteor secara otomatis menjalankan EJSON.stringify(Uint8Array) dan mengirimkannya dengan DDP. Anda dapat memeriksa data di lalu lintas websocket konsol chrome, Anda akan melihat string yang menyerupai base64
Di sisi server, Meteor memanggil EJSON.parse() dan mengubahnya kembali menjadi buffer
Pro
- Sederhana, tanpa peretasan, tanpa paket tambahan
- Tetap pada prinsip Data on the Wire
Kontra
- Lebih banyak bandwidth:string base64 yang dihasilkan ~ 33% lebih besar dari file aslinya
- Batas ukuran file:tidak dapat mengirim file besar (batas ~ 16 MB?)
- Tanpa cache
- Belum ada gzip atau kompresi
- Menghabiskan banyak memori jika Anda memublikasikan file
Opsi 2:XHR, posting dari klien ke sistem file
/*** 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
});
Penjelasan
File di klien diambil, objek XHR dibuat dan file dikirim melalui 'POST' ke server.
Di server, data disalurkan ke sistem file yang mendasarinya. Anda juga dapat menentukan nama file, melakukan sanitasi atau memeriksa apakah sudah ada, dll sebelum menyimpan.
Pro
- Memanfaatkan XHR 2 sehingga Anda dapat mengirim arraybuffer, tidak diperlukan FileReader() baru dibandingkan dengan opsi 1
- Arraybuffer kurang besar dibandingkan dengan string base64
- Tidak ada batasan ukuran, saya mengirim file ~ 200 MB di localhost tanpa masalah
- Sistem file lebih cepat daripada mongodb (lebih banyak lagi nanti di benchmarking di bawah)
- Dapat disimpan dalam cache dan gzip
Kontra
- XHR 2 tidak tersedia di browser lama, mis. di bawah IE10, tetapi tentu saja Anda dapat mengimplementasikan posting tradisional
- /path/to/dir/ harus berada di luar meteor, jika tidak, menulis file di /public akan memicu pemuatan ulang
Opsi 3:XHR, simpan ke 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);
});
Penjelasan
Skrip klien sama seperti pada opsi 2.
Menurut Meteor 1.0.x mongo_driver.js baris terakhir, objek global yang disebut MongoInternals terbuka, Anda dapat memanggil defaultRemoteCollectionDriver() untuk mengembalikan objek db database saat ini yang diperlukan untuk GridStore. Dalam versi A, GridStore juga diekspos oleh MongoInternals. Mongo yang digunakan oleh meteor saat ini adalah v1.4.x
Kemudian di dalam rute, Anda dapat membuat objek tulis baru dengan memanggil var file =new GridStore(...) (API). Anda kemudian membuka file dan membuat aliran.
Saya juga menyertakan versi B. Dalam versi ini, GridStore dipanggil menggunakan drive mongodb baru melalui Npm.require('mongodb'), mongo ini adalah v2.0.13 terbaru pada saat tulisan ini dibuat. API baru tidak mengharuskan Anda untuk membuka file, Anda dapat memanggil stream(true) secara langsung dan mulai melakukan pemipaan
Pro
- Sama seperti pada opsi 2, dikirim menggunakan arraybuffer, lebih sedikit overhead dibandingkan dengan string base64 pada opsi 1
- Tidak perlu khawatir tentang pembersihan nama file
- Pemisahan dari sistem file, tidak perlu menulis ke temp dir, db dapat di-backup, rep, shard dll
- Tidak perlu mengimplementasikan paket lain
- Dapat disimpan dalam cache dan dapat di-gzip
- Menyimpan ukuran yang jauh lebih besar dibandingkan dengan koleksi mongo biasa
- Menggunakan pipa untuk mengurangi kelebihan memori
Kontra
- Grid Mongo Tidak Stabil . Saya menyertakan versi A (mongo 1.x) dan B (mongo 2.x). Di versi A, ketika melakukan pemipaan file besar> 10 MB, saya mendapatkan banyak kesalahan, termasuk file yang rusak, pipa yang belum selesai. Masalah ini diselesaikan di versi B menggunakan mongo 2.x, semoga meteor segera ditingkatkan ke mongodb 2.x
- Kebingungan API . Di versi A, Anda perlu membuka file sebelum dapat melakukan streaming, tetapi di versi B, Anda dapat melakukan streaming tanpa memanggil open. Dokumen API juga tidak terlalu jelas dan alirannya tidak 100% sintaks dapat ditukar dengan Npm.require('fs'). Di fs, Anda memanggil file.on('finish') tetapi di GridFS Anda memanggil file.on('end') saat menulis selesai/berakhir.
- GridFS tidak menyediakan atomisitas penulisan, jadi jika ada beberapa penulisan bersamaan ke file yang sama, hasil akhirnya mungkin sangat berbeda
- Kecepatan . Mongo GridFS jauh lebih lambat daripada sistem file.
Tolok ukur Anda dapat melihat di opsi 2 dan opsi 3, saya menyertakan var start =Date.now() dan saat menulis end, saya console.log out waktu dalam ms , di bawah ini adalah hasilnya. Dual Core, ram 4 GB, HDD, berbasis ubuntu 14.04.
file size GridFS FS
100 KB 50 2
1 MB 400 30
10 MB 3500 100
200 MB 80000 1240
Anda dapat melihat bahwa FS jauh lebih cepat daripada GridFS. Untuk file 200 MB, dibutuhkan ~80 detik menggunakan GridFS tetapi hanya ~ 1 detik di FS. Saya belum mencoba SSD, hasilnya mungkin berbeda. Namun, dalam kehidupan nyata, bandwidth dapat menentukan seberapa cepat file dialirkan dari klien ke server, mencapai kecepatan transfer 200 MB/detik bukanlah hal yang biasa. Di sisi lain, kecepatan transfer ~2 MB/dtk (GridFS) lebih normal.
Kesimpulan
Ini tidak berarti komprehensif, tetapi Anda dapat memutuskan opsi mana yang terbaik untuk kebutuhan Anda.
- DDP adalah yang paling sederhana dan berpegang pada prinsip inti Meteor tetapi datanya lebih besar, tidak dapat dikompresi selama transfer, tidak dapat disimpan dalam cache. Namun opsi ini mungkin bagus jika Anda hanya membutuhkan file kecil.
- XHR digabungkan dengan sistem file adalah cara 'tradisional'. API yang stabil, cepat, 'streamable', dapat dikompresi, dapat disimpan dalam cache (ETag dll), tetapi harus berada di folder terpisah
- XHR digabungkan dengan GridFS , Anda mendapatkan manfaat dari rep set, scalable, tidak ada dir sistem file yang menyentuh, file besar dan banyak file jika sistem file membatasi jumlahnya, juga dapat dikompresi dalam cache. Namun, API tidak stabil, Anda mendapatkan kesalahan dalam banyak penulisan, ini adalah s..l..o..w..
Semoga segera, meteor DDP bisa support gzip, caching dll dan GridFS bisa lebih cepat ...