Поскольку система веб-сайтов становится все больше и больше, может потребоваться более или менее совместное использование файлов cookie с разных доменных имен и даже с разных партнерских веб-сайтов. Когда вы сталкиваетесь с этой ситуацией, каждый обычно думает об использовании центра входа в систему для распространения статуса файлов cookie. а затем синхронизировать его. Решение, стоимость выше, а реализация сложнее и хлопотнее.
Поскольку файлы cookie являются междоменными, браузеры вообще не разрешают взаимный доступ. Чтобы обойти это ограничение, используется следующий план реализации для обмена данными между доменами с использованием postmessage и localstorage.
Принцип относительно простой, но встречается много подводных камней. Давайте здесь разберемся и сделаем бэкап.
2. Дизайн APIКак упоминалось выше, мы используем localstorage вместо файлов cookie. Существуют некоторые различия в использовании между localstorage и cookie. Например, localstorage имеет большую емкость, но не имеет срока действия. Хотя емкость большая, она доступна. различные браузеры. Верхний предел пространства позволяет легко выйти из строя, если операция не работает. Кроме того, хотя postmessage поддерживает междоменный режим, проблемы безопасности и асинхронный API также создают некоторые проблемы в использовании. проще в использовании?
Давайте сначала посмотрим на API, который я разработал:
import { crosData } from 'base-tools-crossDomainData';var store = new crosData({ iframeUrl:somefile.html, //Общий адрес iframe, у iframe особые требования, см. файл шаблона expire:'d,h,s' / /Срок действия по умолчанию в днях, часах, секундах, также может быть перезаписан при установке});store.set('key','val',{ expire:'d,h,s' //option Может привести к сроку действия, переопределить expire}).then((data)=>{ //Асинхронный метод, в случае сбоя он введет событие catch //data {val:'val',key:'key',domain :'domain'};}).catch((err)=>{ console.log(err);}); store.get('key',{domain:'(.*).sina.cn' //Вы можете указать имя домена или использовать (.*) для соответствия обычным строкам. Возвращаемая информация val будет включать информацию о домене. Если она не заполнена, она вернет локальный домен }).then((vals) =>{ console.log (val) //Получаем сохраненные данные асинхронно, их может быть несколько, это массив [{},{}]}).catch((err)=>{});store.clear ('ключ').then(). catch(); //Очистить только ключи в текущем домене. Ключи в других доменах не могут быть очищены. Их можно только читать.Быстрота использования модуля зависит от API, поэтому для модуля совместного использования данных я думаю, что можно поддерживать три метода установки, получения и очистки, поскольку само по себе почтовое сообщение представляет собой одноразовое асинхронное поведение. и это должно быть упаковано в обещание. Более подходящее и простое в использовании. Поскольку localstorage не поддерживает время истечения срока действия, требуется глобальная конфигурация срока действия. Конечно, ее также можно настроить индивидуально во время установки. При получении мы можем указать получение данных в определенном домене или данных в нескольких доменах, поскольку имена ключей. могут повторяться, но домен только один. Это включает в себя управление данными. Давайте поговорим об этом отдельно позже. Наконец, ясные и настроенные API могут размещать данные только в этом домене и не могут работать с данными в других доменах.
Давайте посмотрим на настройки клиента и API:
<!DOCTYPE html><html> <head> <meta charset=utf-8> <title>crosData</title> </head> <body> <script> window.CROS = { домен:/(.*). sina.cn/, //Или разрешенное вами доменное имя поддерживает обычные символы и * подстановочные знаки lz:false //Включить ли lz-сжатие val-символов}; src=http://cdn/sdk.js></script> </body></html>Можно гибко внедрить клиентский js sdk в html документ в любом домене, а потом настроить белый список домена, который позволяет через глобальные атрибуты подсаживаться в домен, где этот документ находится. Он поддерживает регулярные выражения, а дальше лз есть ли. Запустите сжатие lz-строки. Что такое сжатие lz, я расскажу позже.
На этом относительно общий проект API завершен. Давайте рассмотрим принципы реализации и некоторые конкретные проблемы.
3. Принцип реализацииЗвучит очень просто, но на самом деле это не написано. Сначала нам нужно знать, как использовать postMessage. Это очень распространенный API. Здесь нужно сказать вам один важный момент: postMessage можно использовать только в iframe. или использование окна. .open — это способ открыть новую страницу для общения друг с другом. Конечно, здесь нам сначала нужно создать скрытый iframe для междоменного доступа.
Мне лень пользоваться инструментами для рисования картинок, потому что процесс относительно понятен. Здесь я перескажу весь процесс общения словами. Сначала родительская страница создает скрытый iframe, а потом при выполнении команд типа set, get,clear. и т. д., сообщение транслируется через postMessage. После того, как страница получает сообщение, она анализирует команду, данные и идентификатор обратного вызова (postMessage не может передавать функции и ссылки из-за проблем совместимости. Лучше всего передавать только строковый тип). , поэтому данные необходимо преобразовать в строку). Затем, когда дочерняя страница завершает операцию localstorage, она возвращает соответствующий cbid и данные родительской странице через postMessage. Родительская страница прослушивает событие сообщения и обрабатывает результаты.
4. КодированиеНу и так строк всего несколько, приступим к кодированию:
Во-первых, давайте представим, какие сторонние пакеты мы используем и почему мы их используем:
1. url-parse анализирует URL-адрес, в основном используя в нем атрибут origin, поскольку сам postMessage имеет строгую проверку происхождения, а также нам необходимо поддерживать белый список и управление доменными именами.
2. ms — это библиотека инструментов для преобразования сокращений времени в миллисекунды.
3. lz-string — это набор инструментов для сжатия строк. Вот научно-популярное введение в алгоритм сжатия LZ. Во-первых, чтобы понять LZ, вам нужно понять RLZ, кодирование длины, который представляет собой очень простой алгоритм сжатия без потерь. Он заменяет повторяющиеся байты простым описанием повторяющихся байтов и количества повторений. Идея алгоритма сжатия LZ заключается в использовании алгоритма RLE для замены предыдущего появления ссылки на ту же последовательность байтов. Проще говоря, алгоритм LZ считается алгоритмом сопоставления строк. Например: определенная строка часто встречается в фрагменте текста и может быть представлена указателем строки, который появляется в предыдущем тексте.
Преимущество самой lz-строки заключается в том, что она может значительно уменьшить емкость вашего хранилища. Если локальное хранилище объемом 5 МБ используется для хранения данных нескольких доменных имен, оно будет сжато и быстро израсходовано. медленнее и требует больше денег. Большой. Если у вас есть требования к размеру передаваемых данных на работе, вы можете попробовать использовать этот алгоритм сжатия для оптимизации длины строки. По умолчанию он не включен.
4. Сам API localstorage store2 относительно прост. Чтобы уменьшить сложность логики кода, для выполнения операций хранилища выбрана популярная библиотека реализации localstorage.
Поговорив о стороннем пакете, давайте посмотрим, как написать js родительской страницы:
класс crosData { конструктор (опции) { supportCheck (); this.options = Object.assign ({ iframeUrl: '', expire: '30d' }, this.cid = 0; this.cbs = {}; .iframeBeforeFuns = []; this.parent = окно; this.origin = новый URL (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; //Я получаю только сообщение открытого iframe, остальные недопустимы, и об ошибке будет сообщено напрямую if (origin !== this.origin) { ignore('незаконное происхождение!'); return; } if (data.err) { this.cbs[data.cbid].reject(data.err); else { this.cbs[data.cbid].resolve(data) .ret); } delete this.cbs[data.cbid] }); } createIframe(url) { addEvent(document, 'domready', () => { var Frame = document.createElement('iframe');frame.style.cssText = 'ширина:1px;высота:1px;граница:0;позиция:абсолютная;слева:-9999px;сверху:-9999px;';frame.setAttribute( 'src', URL);frame.onload = () => { this.child =frame.contentWindow; this.iframeBeforeFuns.forEach(item => item()); } document.body.appendChild(frame }); } postHandle(type, args) { return new Promise((resolve, ignore) => { var cbid =); this.cid var message = {cbid: cbid, origin: новый URL(location.href).origin, action: type, args: args } this.child.postMessage(JSON.stringify(message), this.origin); this.cbs[cbid] = { разрешить, отклонить } this.cid++; }); send(type, args) { return new Promise(resolve). => { if (this.child) { return this.postHandle(type, args).then(resolve) else { var self = this; this.iframeBeforeFuns.push(function() { self.postHandle(type, args).then(resolve); } }) } set(key, val, options) { options = Object.assign({ expire: ms) (this.options.expire) }, options); return this.send('set', [key, val, options]); get(key, options) { options = Object.assign({домен: новый URL(location.href).origin }, options); return this.send('get', [key, options]); Clear(key) { return this.send('clear). ', [ключ]); }}Вероятно, существует всего несколько методов. Вот несколько ключевых моментов, позвольте мне рассказать о них.
1. Методы get, set иclear единообразно называются методами send, но часть параметров дополнена.
2. Метод send возвращает объект-обещание. Если iframe успешно загружен, метод postHandle вызывается напрямую для выполнения операции postMessage. Если iframe все еще загружается, текущая операция передается в массив iframeBeforeFuns, завернутый в массив. и ожидает завершения загрузки iframe. После унифицированного вызова функция также оборачивается методом postHandle.
3. Метод postHandle оборачивает данные перед отправкой запроса и генерирует cbid, origin, action и args. Объект cbs сохраняет разрешение и отклонение для каждого cbid и ожидает возврата сообщения postMessage подстраницы перед обработкой. Поскольку postMessage не может сохранять ссылки и передавать функции, для ассоциации здесь выбран этот метод.
4. Конструктор легко понять. Когда этот класс инициализируется, мы определяем некоторые необходимые нам атрибуты параметров, создаем iframe, затем слушаем событие сообщения и обрабатываем сообщение, возвращаемое подстраницей.
5. В событии сообщения родительской страницы нам необходимо убедиться, что отправленное мне сообщение должно быть окном iframe, которое я открыл, в противном случае будет сообщено об ошибке, а затем разрешение и отклонение в cbs будут выполнены в соответствии с идентификатор ошибки в данных.
6. В методе createIframe обратный вызов при загрузке iframe обрабатывает вызывающий метод кэширования перед созданием. Обратите внимание на использование здесь domready, поскольку SDK может быть выполнен до анализа тела.
Вот код дочерней части:
class iframe { set(key, val, options, origin) { //Проверьте размер val, который не может превышать 20 КБ val = val.toString(); val = this.lz ? lzstring.compressToUTF16(val) : var; valsize = sizeof(val, 'utf16'); //localStorage хранит байты, используя кодировку utf16 if (valsize > this.maxsize) { return { err: 'значение вашего хранилища: ' + valstr + ' размер равен ' + valsize + 'b, maxsize :' + this.maxsize + 'b , используйте utf16' } } key = `${this.prefix}_${key}, $ {new url(origin).origin}`; var data = { val: val, последний раз: Date.now(), expire: Date.now() + options.expire }; store.set(key, data); //Если оно превышает максимальное количество хранилища, удаляем последнее обновление if (store.size() > this.storemax) { varkeys = store.keys(); keys.sort((a, b) => { var item1 = store.get(a), item2 = store.get(b); return item2.lasttime - item1.lasttime; }); это.storemax - store.size()); while (removesize) { store.remove(keys.pop()); removesize--; } return { ret: data } } get(key, options) { var message = {}; ключи = store.keys(); var regexp = new RegExp('^' + this.prefix + '_' + key + ',' + options.domain + '$'); keys.filter((key) => { return regexp.test(key); }).map((storeKey) => { var data = store.get(storeKey); data.key = key; data.domain = storeKey .split(',')[1]; if (data.expire < Date.now()) { store.remove(storeKey); return undefined; else { //Обновление в последний раз; val: data.val, последний раз: Date.now(), expire: data.expire }); data.val = this.lz ? lzstring.decompressFromUTF16(data.val): вернуть данные }). filter(item => { return !!item; //Фильтр не определен }); Clear(key, origin) { store.remove(`${this.prefix}_${key},${origin}`); return {}; }clearOtherKey() { //Удалить недопустимые ключи varkeys(); new RegExp('^' + this.prefix);keys.forEach(key => { if (!keyReg.test(key)) {store.remove(key); } }); } конструктор(safeDomain, lz) { supportCheck(); this.safeDomain = SafeDomain || /.*/; this.prefix = '_cros'; this.clearOtherKey(); if (Object.prototype.toString.call(this. SafeDomain) !== '[object RegExp]') { throw new Error('safeDomain должно быть регулярным выражением'); this.lz = lz; this.storemax = 100; this.maxsize = 20 * 1024; //byte addEvent(window, 'message', (evt) => { var data = JSON.parse(evt.data); var originHostName = новый URL (evt) .origin).hostname; var origin = evt.origin, action = data.action, cbid = data.cbid, args = data.args; //Юридическая рассылка if (evt.origin === data.origin && this.safeDomain.test(originHostName)) { args.push(origin); var whiteAction = ['set', 'get', 'clear'] ; if (whiteAction.indexOf(action) > -1) { var message = this[action].apply(this, args); window.top.postMessage(JSON.stringify(message), origin); } } else { window.top.postMessage(JSON.stringify({ cbid: cbid, err: 'Незаконный домен' }), origin); ; }}Кода немного. Вот краткое описание использования и организационных взаимосвязей каждого метода:
1. В части конструктора приведенный выше класс также проверяет поддержку функций браузера, а затем определяет такие атрибуты, как значение префикса хранилища, максимальное количество и максимальный размер каждого ключа. Затем мы создаем канал сообщений и ждем, пока родительская страница его вызовет.
2. В сообщении мы проверяем происхождение трансляции, затем проверяем вызываемый метод, вызываем соответствующие методы set, get иclear, затем получаем результат выполнения, привязываем cbid и, наконец, отправляем обратно родительскую страницу postMessage.
3. ClearOtherKey удаляет некоторые незаконные данные хранилища и сохраняет только те данные, которые соответствуют формату.
4. В методе set проверка размера и lz-сжатие выполняются для каждого фрагмента данных. Сохраненные данные включают в себя значение, ключ, время истечения срока действия и время обновления (используется для расчета LRU).
5. В методе set, если количество сохраненных ls превышает максимальный предел, в это время требуется операция удаления LRU — это аббревиатура Least Early Used, то есть наименее использованный в последнее время. Мы просматриваем все значения ключей, сортируем значения ключей, передаем последний раз, а затем выполняем операцию извлечения массива ключей, чтобы получить ключи, которые необходимо очистить в конце стека, а затем удаляем их один за другим. .
6. В методе get мы обходим все значения ключей, сопоставляем ключ домена, который нам нужно получить, а затем дизассемблируем ключ в возвращаемом значении (храним его в формате ключа и домена), т.к. API требует возврата нескольких совпадающих значений. Мы делаем окончательный фильтр для просроченных данных, а затем используем lz для распаковки значения val, чтобы гарантировать, что пользователь получает правильный результат.
Выше представлен общий процесс кодирования реализации и его обзор. Давайте поговорим о встречающихся подводных камнях.
5. Некоторые подводные камни, с которыми пришлось столкнутьсяПоскольку выше приведен только основной код, это не полный код. Поскольку сама логика относительно понятна, ее можно написать за небольшое время. О подводных камнях поговорим ниже.
1. Рассчитайте объем хранилища localstorage.
Поскольку мы все знаем, что существует ограничение в 5 МБ, максимальное требование для каждого фрагмента данных не может превышать 20 * 1024 байт. Для расчета байтов localstorage необходимо использовать кодировку utf16. См. эту статью: Вычисление символов в JS. Количество секций, занимаемых строками
2. Совместимость
Лучше всего передавать строки в postMessage в IE8. События необходимо сглаживать, а JSON необходимо сглаживать.
3. Асинхронная обработка при создании iframe
Здесь мы ранее сделали рекурсивное ожидание setTimeout, но позже изменили его на указанный выше метод реализации. После загрузки повторная обработка промиса обрабатывается единообразно, чтобы обеспечить унификацию API промиса.
4. При сохранении данных соотношение пространственной сложности и временной сложности.
Первая версия не является вышеуказанной реализацией, я реализовал 3 версии:
В первой версии сохраняется массив LRU, чтобы уменьшить временную сложность, но при этом теряется сложность пространства. Более того, после тестирования метод получения хранилища занимает относительно много времени, в основном из-за трудоемкости анализа.
Во второй версии, чтобы максимизировать степень сжатия lz-строки, я сохранил все данные, включая массив LRU, в ключевое значение. В результате lz-string и getItem затраты времени на анализ очень велики, когда они есть. данных много. Хотя временная сложность расчета самая низкая.
Последняя версия приведена выше. Я пожертвовал некоторой временной сложностью и пространственной сложностью, но поскольку узким местом является скорость чтения и записи операций set и get, скорость чтения и записи одного сохранения чрезвычайно высока. Ключи потому, что используется нижний уровень. Для локального хранилища производительность по-прежнему очень хорошая. 100 записей можно хранить в 20 КБ, а время чтения и записи составляет около 1 секунды. Производительность очень хорошая.
6. Резюме и сравнениеПосле написания модуля я понял, что есть такая библиотека: zendesk/cross-storage
Но я проверил его API и исходный код и сравнил методы реализации, думаю, моя версия важнее.
1. Моя версия контролирует доменное имя и управление данными.
2. Моя версия Promise API более упрощена, в ней на один onConnect меньше. Вы можете сослаться на его реализацию. Это гораздо больше, чем то, что я написал, и она не решает проблему асинхронного ожидания iframe.
3. Данные, сжатые LZ, не поддерживаются.
4. Управление пулом хранения LRU не поддерживается, поэтому может быть слишком много памяти, что может привести к сбою записи.
5. Кажется, он создает iframe для каждого взаимодействия, что является пустой тратой операций DOM и трансляции. Я думаю, что нет проблем, если оставить его включенным. Конечно, ему может потребоваться подключиться к нескольким клиентам, поэтому он справляется с этим таким образом. .
Подвести итогВышеизложенное представляет собой введение редактора в проблему использования локального хранилища вместо файлов cookie для реализации междоменного обмена данными. Надеюсь, это будет вам полезно. Если у вас есть какие-либо вопросы, оставьте мне сообщение, и редактор ответит вам. вовремя. Я также хотел бы поблагодарить всех за поддержку сайта боевых искусств VeVb!