Karena sistem situs web semakin besar, cookie dari nama domain yang berbeda dan bahkan situs web mitra yang berbeda mungkin perlu dibagikan lebih banyak atau lebih sedikit. Ketika menghadapi situasi ini, yang biasanya dipikirkan semua orang adalah menggunakan pusat login untuk mendistribusikan status cookie dan kemudian menyinkronkannya Solusinya, biayanya lebih tinggi dan implementasinya lebih rumit dan merepotkan.
Karena cookie bersifat lintas domain, browser tidak mengizinkan akses bersama sama sekali. Untuk menerobos pembatasan ini, rencana implementasi berikut digunakan untuk berbagi data di seluruh domain menggunakan postmessage dan penyimpanan lokal.
Prinsipnya relatif sederhana, tetapi ada banyak kendala yang ditemui. Mari kita selesaikan di sini dan membuat cadangan.
2. Desain APISeperti disebutkan di latar belakang, kami menggunakan penyimpanan lokal dan bukan cookie. Ada beberapa perbedaan dalam penggunaan antara penyimpanan lokal dan cookie. Misalnya, penyimpanan lokal memiliki kapasitas yang lebih besar, namun tidak ada waktu kedaluwarsa browser yang berbeda. Batas atas ruang membuatnya mudah crash jika pengoperasiannya tidak baik. Selain itu, meskipun postmessage mendukung lintas domain, masalah keamanan dan API asinkron juga membawa beberapa masalah untuk digunakan lebih mudah digunakan?
Mari kita lihat API yang saya rancang pertama kali:
import { crosData } dari 'base-tools-crossDomainData';var store = new crosData({ iframeUrl:somefile.html, //Alamat iframe bersama, iframe memiliki persyaratan khusus, lihat file templat kedaluwarsa:'d,h,s' / /Waktu kedaluwarsa default dalam hari, jam, detik, juga dapat ditimpa saat menanam});store.set('key','val',{ expired:'d,h,s' //option Dapat menghadirkan waktu kedaluwarsa, override expired}).then((data)=>{ //Metode asynchronous, jika gagal maka akan masuk ke event catch //data {val:'val',key:'key',domain :' domain'};}).catch((err)=>{ console.log(err);}); store.get('key',{ domain:'(.*).sina.cn' //Anda dapat menentukan nama domain, atau Anda dapat menggunakan (.*) untuk mencocokkan string biasa. Informasi val yang dikembalikan akan menyertakan informasi domain. Jika tidak diisi, maka akan mengembalikan domain lokal }).then((vals) =>{ console.log (val) //Dapatkan data yang disimpan secara asinkron, mungkin ada banyak, berupa array [{},{}]}).catch((err)=>{});store.clear ('kunci').lalu(). //Hanya menghapus kunci pada domain saat ini. Kunci pada domain lain tidak boleh dihapus.Cepat atau tidaknya suatu modul digunakan bergantung pada API, jadi untuk modul berbagi data, menurut saya tidak apa-apa untuk mendukung tiga metode set, dapatkan, dan hapus, karena postmessage sendiri adalah perilaku asinkron yang dilakukan satu kali, dan itu harus dikemas menjadi sebuah janji. Lebih cocok dan lebih mudah digunakan. Karena penyimpanan lokal tidak mendukung waktu kedaluwarsa, diperlukan konfigurasi waktu kedaluwarsa global. Tentu saja, ini juga dapat dikonfigurasi secara individual selama pengambilan, kita dapat menentukan untuk memperoleh data dalam domain tertentu atau data dalam beberapa domain, karena Nama kunci boleh diulang, tapi hanya ada satu domain. Ini melibatkan pengelolaan data. Mari kita bahas secara terpisah nanti. Terakhir, API yang jelas dan disetel hanya dapat menanam data di domain ini dan tidak dapat mengoperasikan data di domain lain.
Mari kita lihat pengaturan klien dan API:
<!DOCTYPE html><html> <head> <meta charset=utf-8> <title>crosData</title> </head> <body> <script> window.CROS = { domain:/(.*). sina.cn/, //Atau nama domain yang Anda izinkan, mendukung wildcard reguler dan * lz:false //Apakah akan mengaktifkan kompresi lz karakter val}; </script> <script src=http://cdn/sdk.js></script> </body></html>Anda dapat secara fleksibel memasukkan js SDK klien ke dalam dokumen html di domain mana pun, dan kemudian mengonfigurasi daftar putih domain yang memungkinkan Anda ditanam di domain tempat dokumen ini berada melalui atribut global. Ini mendukung ekspresi reguler, dan kemudian lz adalah apakah Mulai kompresi lz-string. Saya akan memperkenalkan apa itu kompresi lz nanti.
Pada titik ini, desain API yang relatif umum telah selesai. Mari kita lihat prinsip implementasi dan beberapa masalah spesifik.
3. Prinsip pelaksanaanKedengarannya sangat sederhana, tetapi sebenarnya tidak tertulis. Pertama-tama kita perlu mengetahui cara menggunakan postMessage. Ini adalah API yang sangat umum. Ada satu hal penting yang perlu diberitahukan kepada Anda di sini, yaitu postMessage hanya dapat digunakan di iframe atau menggunakan jendela. .open adalah cara membuka halaman baru untuk berkomunikasi satu sama lain. Tentu saja di sini kita perlu membuat iframe tersembunyi untuk lintas domain terlebih dahulu.
Saya terlalu malas menggunakan alat untuk menggambar, karena prosesnya relatif jelas. Di sini saya akan menceritakan kembali seluruh proses komunikasi dengan kata-kata. Pertama, halaman induk membuat iframe tersembunyi, dan kemudian ketika perintah seperti set, get, clear , dll. dieksekusi, pesan disiarkan melalui postMessage. Setelah halaman menerima pesan, halaman akan mem-parsing perintah, data, dan ID panggilan balik (postMessage tidak dapat meneruskan fungsi dan referensi karena masalah kompatibilitas. Yang terbaik adalah hanya meneruskan tipe string , jadi datanya perlu dirangkai). Kemudian ketika halaman anak menyelesaikan operasi penyimpanan lokal, ia mengembalikan cbid dan data yang sesuai ke halaman induk melalui postMessage. Halaman induk mendengarkan peristiwa pesan dan memproses hasilnya.
4. PengkodeanNah, jadi hanya ada beberapa baris, mari kita mulai coding:
Pertama, mari perkenalkan paket pihak ketiga apa yang kami gunakan dan alasan kami menggunakannya:
1. url-parse mem-parsing url, terutama menggunakan atribut origin di dalamnya, karena postMessage sendiri memiliki verifikasi asal yang ketat, dan kita juga perlu mendukung daftar putih dan manajemen nama domain.
2. ms adalah pustaka alat untuk mengubah singkatan waktu menjadi milidetik.
3. lz-string adalah toolkit untuk mengompresi string. Berikut adalah pengenalan sains populer tentang algoritma kompresi LZ. Pertama, untuk memahami LZ, Anda perlu memahami RLZ, Run Long Encoding, yang merupakan algoritma yang sangat sederhana untuk kompresi lossless. Ini menggantikan byte berulang dengan deskripsi sederhana tentang byte berulang dan jumlah pengulangan. Ide di balik algoritma kompresi LZ adalah dengan menggunakan algoritma RLE untuk menggantikan kemunculan referensi sebelumnya ke urutan byte yang sama. Sederhananya, algoritma LZ dianggap sebagai algoritma pencocokan string. Misalnya: string tertentu sering muncul pada suatu teks dan dapat diwakili oleh penunjuk string yang muncul pada teks sebelumnya.
Kelebihan dari lz-string sendiri adalah dapat mengurangi kapasitas penyimpanan Anda secara signifikan. Jika penyimpanan lokal sebesar 5MB digunakan untuk mendukung penyimpanan data beberapa nama domain, maka akan terkompresi dan cepat habis lebih lambat dan menghabiskan lebih banyak uang. Jika Anda memiliki persyaratan ukuran untuk jumlah data yang akan dikirim di tempat kerja, Anda dapat mencoba menggunakan algoritma kompresi ini untuk mengoptimalkan panjang string.
4. API penyimpanan lokal dari store2 sendiri relatif sederhana. Untuk mengurangi kompleksitas logika kode, perpustakaan implementasi penyimpanan lokal yang populer dipilih untuk melakukan operasi penyimpanan.
Setelah membicarakan tentang paket pihak ketiga, mari kita lihat cara menulis js halaman induk:
kelas crosData { konstruktor(opsi) { supportCheck(); this.options = Objek.assign({ iframeUrl: '', kedaluwarsa: '30d' }, opsi); .iframeBeforeFuns = []; this.parent = jendela; this.origin = url baru(this.options.iframeUrl).origin; this.createIframe(this.options.iframeUrl); addEvent(this.parent, 'message', (evt) => { var data = JSON.parse(evt.data); var origin = evt.origin || evt.originalEvent .origin; //Saya hanya menerima pesan dari iframe yang saya buka, yang lain ilegal, dan kesalahan akan langsung dilaporkan jika (origin !== this.origin) { tolak('asal ilegal!'); return; if (data.err) { this.cbs[data.cbid].reject(data.err } else { this.cbs[data.cbid].resolve(data .ret); } hapus ini.cbs[data.cbid]; } createIframe(url) { addEvent(dokumen, 'domready', () => { var frame = document.createElement('iframe'); frame.style.cssText = 'lebar:1px;tinggi:1px;batas:0;posisi:absolute;kiri:-9999px;atas:-9999px;'; 'src', url); frame.onload = () => { this.child = frame.contentWindow; this.iframeBeforeFuns.forEach(item => item()); } document.body.appendChild(frame }); } postHandle(type, args) { kembalikan Janji baru((putuskan, tolak) => { var cbid = this.cid; var pesan = { cbid: cbid, asal: url baru(lokasi.href).origin, tindakan: ketik, args: args } this.child.postMessage(JSON.stringify(message), this.origin); this.cbs[cbid] = { tekad, tolak } this.cid++ } } kirim(ketik, args) { kembalikan Janji baru(resolve => { if (this.child) { return this.postHandle(type, args).then(resolve); this.iframeBeforeFuns.push(function() { self.postHandle(type, args).then(resolve); }); } }) } set(key, val, options) { options = Object.assign({ expired: ms (ini.pilihan.kedaluwarsa) }, opsi); kembalikan ini.kirim('set', [kunci, val, opsi]); Objek.menetapkan({ domain: url baru(lokasi.href).asal }, opsi); kembalikan ini.kirim('dapatkan', [kunci, opsi] } hapus(kunci) { kembalikan ini.kirim('hapus ', [kunci]); }}Mungkin hanya ada beberapa metode. Berikut adalah beberapa poin penting, izinkan saya membicarakannya.
1. Metode get, set, dan clear semuanya disebut metode kirim, tetapi bagian opsinya dilengkapi.
2. Metode kirim mengembalikan objek janji. Jika iframe berhasil dimuat, metode postHandle langsung dipanggil untuk melakukan operasi postMessage. Jika iframe masih dimuat, operasi saat ini didorong ke array iframeBeforeFuns, dibungkus dengan a fungsi, dan menunggu onload iframe berakhir. Setelah panggilan terpadu, fungsi tersebut juga digabungkan dalam metode postHandle.
3. Metode postHandle membungkus data sebelum mengirim permintaan dan menghasilkan cbid, origin, action dan args. Objek cbs menyimpan penyelesaian dan penolakan di bawah setiap cbid, dan menunggu postMessage dari sub-halaman kembali sebelum diproses. Karena postMessage tidak dapat menyimpan referensi dan tidak dapat meneruskan fungsi, metode ini dipilih di sini untuk asosiasi.
4. Konstruktornya mudah dimengerti. Saat kelas ini diinisialisasi, kita mendefinisikan beberapa atribut opsi yang kita perlukan, membuat iframe, kemudian mendengarkan event pesan dan memproses pesan yang dikembalikan oleh subhalaman.
5. Dalam acara pesan di halaman induk, kita perlu memverifikasi bahwa pesan yang dikirimkan kepada saya harus berupa jendela iframe yang saya buka, jika tidak, kesalahan akan dilaporkan, dan penyelesaian dan penolakan di cbs akan dijalankan sesuai dengan pengidentifikasi kesalahan dalam data.
6. Pada metode createIframe, callback di iframe onload menangani pemanggilan metode caching sebelum pembuatan. Perhatikan penggunaan domready di sini, karena SDK dapat dieksekusi sebelum isi diurai.
Berikut ini kode untuk bagian anak:
class iframe { set(key, val, options, origin) {//Periksa ukuran val, yang tidak boleh melebihi 20k. val = val.toString(); valsize = sizeof(val, 'utf16'); //localStorage menyimpan byte menggunakan pengkodean utf16 if (valsize > this.maxsize) { return { err: 'nilai toko Anda : ' + valstr + ' size adalah ' + valsize + 'b, maxsize :' + this.maxsize + 'b , gunakan utf16' } } key = `${this.prefix}_${key}, ${url baru(asal).asal}`; var data = { val: val, waktu terakhir: Tanggal.sekarang(), masa berlaku: Tanggal.sekarang() + opsi.kedaluwarsa }; store.set(key, data); //Jika lebih besar dari jumlah penyimpanan maksimum, hapus yang terakhir diperbarui if (store.size() > this.storemax) { var key = store.keys(); kunci.sort( (a, b) => { var item1 = store.get(a), item2 = store.get(b); kembalikan item2.lasttime - item1.lasttime; }); ini.storemax - store.size()); while (hapus ukuran) { store.remove(keys.pop()); hapus ukuran--; } } return { ret: data } } dapatkan(kunci, opsi) { var pesan = {}; kunci = toko.kunci(); var regexp = RegExp baru('^' + awalan ini + '_' + kunci + ',' + opsi.domain + '$'); kunci.filter((kunci) => { return regexp.test(key); }).map((storeKey) => { var data = store.get(storeKey); data.key = kunci; data.domain = storeKey .split(',')[1]; if (data.expire < Date.now()) { store.remove(storeKey); kembalikan tidak ditentukan; } else { //Perbarui terakhir kali; val: data.val, terakhir kali: Tanggal.sekarang(), kedaluwarsa: data.expire }); } data.val = this.lz ? lzstring.decompressFromUTF16(data.val) : data.val; filter(item => { return !!item; //Filter tidak terdefinisi }); store.remove(`${this.prefix}_${key},${origin}`); return {}; } clearOtherKey() { //Hapus kunci ilegal var kunci = store.keys(); RegExp baru('^' + this.prefix); kunci.forEach(kunci => { if (!keyReg.test(kunci)) { store.remove(kunci); } }); } konstruktor(safeDomain, lz) { supportCheck(); this.safeDomain = safeDomain ||. /.*/; this.prefix = '_cros'; safeDomain) !== '[objek RegExp]') { throw new Error('safeDomain harus regexp'); this.storemax = 100; this.maxsize = 20 * 1024; //byte addEvent(jendela, 'pesan', (evt) => { var data = JSON.parse(evt.data); var originHostName = url baru( evt .origin).nama host; var asal = evt.origin, tindakan = data.aksi, cbid = data.cbid, args = data.args; //Siaran resmi if (evt.origin === data.origin && this.safeDomain.test(originHostName)) { args.push(origin); var whiteAction = ['set', 'get', 'clear'] ; if (whiteAction.indexOf(action) > -1) { var pesan = ini[aksi].berlaku(ini, pesan.cbid = cbid; window.top.postMessage(JSON.stringify(pesan), asal); } } else { window.top.postMessage(JSON.stringify({ cbid: cbid, err: 'Domain ilegal' }), asal); ;Tidak banyak kode. Berikut adalah pengenalan singkat tentang penggunaan dan hubungan organisasi masing-masing metode:
1. Pada bagian konstruktor, kelas di atas juga memeriksa dukungan fitur browser, lalu mendefinisikan atribut seperti nilai awalan penyimpanan, jumlah maksimum, dan ukuran maksimal setiap kunci. Kemudian kita membuat saluran pesan dan menunggu halaman induk memanggilnya.
2. Dalam pesan tersebut, kita memeriksa asal siaran, kemudian memeriksa metode yang dipanggil, memanggil metode set, get, dan clear yang sesuai, kemudian mendapatkan hasil eksekusi, mengikat cbid, dan terakhir mengirim kembali halaman induk postMessage.
3. clearOtherKey menghapus beberapa data penyimpanan ilegal dan hanya menyimpan data yang sesuai dengan formatnya.
4. Pada metode set, verifikasi ukuran dan kompresi lz dilakukan pada setiap bagian data. Data yang disimpan meliputi val, kunci, waktu kedaluwarsa, dan waktu pembaruan (digunakan untuk perhitungan LRU).
5. Pada metode set, jika jumlah ls yang disimpan melebihi batas maksimum, maka diperlukan operasi penghapusan saat ini LRU adalah singkatan dari Least Almost Used, yaitu yang paling sedikit digunakan. Kami melintasi semua nilai kunci, mengurutkan nilai kunci, meneruskan waktu terakhir, dan kemudian melakukan operasi pop array kunci untuk mendapatkan kunci yang perlu dibersihkan di akhir tumpukan, dan kemudian menghapusnya satu per satu .
6. Dalam metode get, kita melintasi semua nilai kunci, mencocokkan kunci domain yang perlu kita dapatkan, dan kemudian membongkar kunci dalam nilai yang dikembalikan (kita menyimpannya dalam format kunci dan domain), karena API memerlukan beberapa nilai yang cocok untuk dikembalikan. Kami membuat filter terakhir pada data yang kedaluwarsa, dan kemudian menggunakan lz untuk mendekompresi nilai val untuk memastikan bahwa pengguna mendapatkan hasil yang benar.
Di atas adalah keseluruhan proses dan tinjauan pengkodean implementasi kami. Mari kita bahas tentang kendala yang dihadapi.
5. Beberapa kendala yang ditemuiKarena hanya kode utama yang diberikan di atas, bukan kode lengkap. Karena logikanya sendiri relatif jelas, sehingga dapat ditulis dalam waktu yang singkat. Mari kita bahas tentang kendalanya di bawah ini.
1. Hitung nilai penyimpanan penyimpanan lokal.
Karena kita semua tahu bahwa ada batasan 5MB, persyaratan maksimum untuk setiap bagian data tidak boleh melebihi 20*1024 byte. Untuk penghitungan byte, penyimpanan lokal perlu menggunakan pengkodean utf16 untuk konversi Ditempati oleh sejumlah bagian String
2. Kompatibilitas
Yang terbaik adalah meneruskan string di postMessage di bawah IE8. Acara perlu dihaluskan dan JSON perlu dihaluskan.
3. Pemrosesan asinkron saat membuat iframe
Di sini, kami sebelumnya melakukan penantian rekursif untuk setTimeout, tetapi kemudian mengubahnya ke metode implementasi di atas. Setelah onload, pengambilan ulang janji diproses secara seragam untuk memastikan penyatuan API janji.
4. Saat menyimpan data, kompleksitas ruang vs kompleksitas waktu.
Versi pertama bukan implementasi di atas, saya menerapkan 3 versi:
Versi pertama menyimpan array LRU untuk mengurangi kompleksitas waktu, tetapi membuang kompleksitas ruang. Selain itu, setelah pengujian, metode get penyimpanan relatif memakan waktu, terutama karena parse yang memakan waktu.
Pada versi kedua, untuk memaksimalkan tingkat kompresi lz-string, saya menyimpan semua data termasuk array LRU ke nilai kunci. Hasilnya, lz-string dan getItem, konsumsi waktu parse sangat besar ketika ada banyak data. Meskipun perhitungannya Kompleksitas waktu paling rendah.
Versi terakhir adalah yang di atas. Saya mengorbankan beberapa kompleksitas waktu dan kompleksitas ruang, tetapi karena hambatannya terletak pada kecepatan baca dan tulis set dan dapatkan, kecepatan baca dan tulis dari satu penyimpanan sangat cepat kuncinya adalah karena lapisan bawah digunakan Untuk dalam penyimpanan lokal, kinerjanya masih sangat bagus. 100 entri dapat disimpan dalam 20kb, dan waktu baca dan tulis sekitar 1 detik.
6. Ringkasan dan perbandinganSetelah menulis modul, saya menyadari bahwa ada perpustakaan seperti itu: zendesk/cross-storage
Tapi saya memeriksa API dan kode sumbernya dan membandingkan metode implementasi. Saya pikir versi saya lebih penting.
1. Versi saya memiliki kendali atas nama domain dan pengelolaan data.
2. Versi janji api saya lebih disederhanakan, dengan satu onConnect lebih sedikit daripada itu. Anda dapat merujuk pada implementasinya. Ini lebih dari apa yang saya tulis, dan itu tidak menyelesaikan masalah iframe menunggu asynchronous.
3. Data terkompresi LZ tidak didukung.
4. Manajemen kumpulan penyimpanan LRU tidak didukung, sehingga mungkin terdapat terlalu banyak penyimpanan yang dapat menyebabkan kegagalan penulisan.
5. Dia sepertinya membuat iframe untuk setiap interaksi, yang merupakan pemborosan operasi DOM dan penyiaran. Saya rasa tidak ada masalah jika membiarkannya aktif. Tentu saja, dia mungkin perlu terhubung ke banyak klien sehingga dia menanganinya dengan cara ini .
MeringkaskanDi atas adalah pengenalan editor tentang masalah penggunaan penyimpanan lokal alih-alih cookie untuk mewujudkan berbagi data lintas domain. Saya harap ini dapat membantu Anda. Jika Anda memiliki pertanyaan, silakan tinggalkan pesan kepada saya dan editor akan membalas Anda pada waktunya. Saya juga ingin mengucapkan terima kasih kepada semua orang atas dukungan Anda terhadap situs seni bela diri VeVb!