웹사이트 시스템이 점점 더 커지고 있기 때문에 다른 도메인 이름과 심지어 다른 파트너 웹사이트의 쿠키를 어느 정도 공유해야 할 수도 있습니다. 이러한 상황이 발생하면 모든 사람이 일반적으로 로그인 센터를 사용하여 쿠키 상태를 배포하는 것입니다. 솔루션을 동기화하면 비용이 더 많이 들고 구현이 더 복잡하고 번거롭습니다.
쿠키는 크로스 도메인이기 때문에 브라우저는 상호 접근을 전혀 허용하지 않습니다. 이러한 제한을 극복하기 위해 postmessage와 localstorage를 사용하여 도메인 간에 데이터를 공유하는 구현 계획은 다음과 같습니다.
원리는 비교적 간단하지만 여기서는 함정이 많이 발생하여 정리하고 백업해둡니다.
2. API 디자인백그라운드에서 언급했듯이 쿠키 대신 localstorage를 사용합니다. 예를 들어 localstorage는 용량이 크지만 만료 시간은 없습니다. 공간의 상한으로 인해 작동이 좋지 않으면 쉽게 충돌할 수 있습니다. 또한 포스트 메시지가 도메인 간을 지원하지만 보안 문제와 비동기 API도 사용에 몇 가지 문제를 가져옵니다. 사용하기 더 쉬운가요?
먼저 제가 디자인한 API를 살펴보겠습니다.
import { crosData } from 'base-tools-crossDomainData';var store = new crosData({ iframeUrl:somefile.html, //공유 iframe 주소, iframe에는 특별한 요구 사항이 있습니다. 템플릿 파일을 참조하세요. 만료:'d,h,s' / /일, 시간, 초 단위의 기본 만료 시간은 심을 때 덮어쓸 수도 있습니다.});store.set('key','val',{ 만료:'d,h,s' //옵션 만료 시간을 가져오고 만료를 재정의할 수 있습니다.}).then((data)=>{ //비동기 메서드, 실패하면 catch 이벤트를 입력합니다. //data {val:'val',key:'key',domain :' domain'};}).catch((err)=>{ console.log(err);}) store.get('key',{ domain:'(.*).sina.cn' //도메인 이름을 지정하거나 (.*)를 사용하여 일반 문자열과 일치시킬 수 있습니다. 반환된 값에는 도메인 정보가 포함됩니다. 입력하지 않으면 로컬 도메인이 반환됩니다. }).then((vals) =>{ console.log (val) //저장된 데이터를 비동기적으로 가져옵니다. 여러 개가 있을 수 있으며 배열입니다. [{},{}]}).catch((err)=>{});store.clear ('키').then(); //현재 도메인의 키만 삭제합니다. 다른 도메인의 키는 삭제할 수 없습니다.
모듈의 빠른 사용 여부는 API에 따라 다르므로 데이터 공유 모듈의 경우 postmessage 자체가 일회성 비동기 동작이므로 set, get,clear 세 가지 방법을 지원하는 것이 좋다고 생각합니다. 그리고 더 적합하고 사용하기 쉬운 형태로 패키징되어야 합니다. localstorage는 만료 시간을 지원하지 않기 때문에 전역 만료 시간 구성이 필요합니다. 물론 설정 중에 개별적으로 구성할 수도 있습니다. 가져올 때 키 이름 때문에 특정 도메인에서 데이터를 가져오거나 여러 도메인에서 데이터를 가져오도록 지정할 수 있습니다. 반복될 수 있지만 도메인은 하나만 있습니다. 여기에는 데이터 관리가 포함됩니다. 나중에 별도로 이야기하겠습니다. 마지막으로 명확한 API는 이 도메인에만 데이터를 심을 수 있고 다른 도메인에서는 데이터를 조작할 수 없습니다.
클라이언트 설정과 API를 살펴보겠습니다.
<!DOCTYPE html><html> <head> <meta charset=utf-8> <title>crosData</title> </head> <body> <script> window.CROS = { domain:/(.*). sina.cn/, //또는 허용하는 도메인 이름, 일반 및 * 와일드카드 지원 lz:false //val 문자의 lz 압축 활성화 여부} </script> <script src=http://cdn/sdk.js></script> </body></html>
클라이언트의 js SDK를 모든 도메인의 html 문서에 유연하게 도입한 다음 전역 속성을 통해 이 문서가 위치한 도메인에 심을 수 있도록 도메인 화이트리스트를 구성할 수 있으며, 그런 다음 lz는 여부입니다. lz-string 압축을 시작해 보세요. lz 압축이 무엇인지 나중에 소개하겠습니다.
이제 비교적 일반적인 API 설계가 완성되었습니다. 구현 원리와 몇 가지 구체적인 문제를 살펴보겠습니다.
3. 시행원리매우 간단해 보이지만 실제로 작성되지는 않습니다. 먼저 postMessage를 사용하는 방법을 알아야 합니다. 이것은 매우 일반적인 API입니다. 여기서 중요한 점은 postMessage가 iframe에서만 사용될 수 있다는 것입니다. 또는 창을 사용하는 것입니다. .open은 서로 통신하기 위해 새 페이지를 여는 방법입니다. 물론 여기서는 먼저 교차 도메인을 위한 숨겨진 iframe을 만들어야 합니다.
과정이 비교적 명확하기 때문에 도구를 사용하여 그림을 그리는 것이 너무 게으릅니다. 여기서는 먼저 상위 페이지에서 숨겨진 iframe을 만든 다음 set, get,clear와 같은 명령을 사용하여 전체 통신 과정을 말로 설명하겠습니다. 등이 실행되면 메시지는 postMessage를 통해 브로드캐스트됩니다. 페이지는 메시지를 수신한 후 명령, 데이터 및 콜백 ID를 구문 분석합니다(postMessage는 호환성 문제로 인해 함수 및 참조를 전달할 수 없습니다. 문자열 유형만 전달하는 것이 가장 좋습니다. , 따라서 데이터를 문자열화해야 합니다). 그런 다음 하위 페이지가 localstorage 작업을 완료하면 postMessage를 통해 해당 cbid 및 데이터를 상위 페이지에 반환합니다. 상위 페이지는 메시지 이벤트를 수신하고 결과를 처리합니다.
4. 인코딩자, 몇 줄 밖에 안 남았으니 코딩을 시작해 보겠습니다.
먼저 우리가 사용하는 타사 패키지와 이를 사용하는 이유를 소개하겠습니다.
1. url-parse는 postMessage 자체가 출처를 엄격하게 검증하고 화이트리스트 및 도메인 이름 관리도 지원해야 하기 때문에 주로 Origin 속성을 사용하여 URL을 구문 분석합니다.
2. ms는 시간 약어를 밀리초로 변환하는 도구 라이브러리입니다.
3. lz-string은 문자열 압축을 위한 툴킷입니다. 다음은 LZ 압축 알고리즘에 대한 대중적인 과학 소개입니다. 먼저 LZ를 이해하려면 무손실 압축을 위한 매우 간단한 알고리즘인 RLZ(실행 길이 인코딩)를 이해해야 합니다. 반복된 바이트를 반복된 바이트 및 반복 횟수에 대한 간단한 설명으로 대체합니다. LZ 압축 알고리즘의 기본 개념은 RLE 알고리즘을 사용하여 동일한 바이트 시퀀스에 대한 이전 참조 항목을 바꾸는 것입니다. 간단히 말해서 LZ 알고리즘은 문자열 일치 알고리즘으로 간주됩니다. 예를 들어 특정 문자열은 텍스트에 자주 나타나며 이전 텍스트에 나타나는 문자열 포인터로 나타낼 수 있습니다.
lz-string 자체의 장점은 저장 용량을 크게 줄일 수 있다는 것입니다. 5MB의 로컬 저장소를 사용하여 여러 도메인 이름의 데이터 저장을 지원하면 압축되어 빨리 소모됩니다. 더 느리고 더 많은 비용을 소비합니다. 직장에서 전송할 데이터 양에 대한 크기 요구 사항이 있는 경우 이 압축 알고리즘을 사용하여 문자열 길이를 최적화할 수 있습니다.
4. store2 자체의 localstorage API는 비교적 간단합니다. 코드 로직의 복잡성을 줄이기 위해 인기 있는 localstorage 구현 라이브러리를 선택하여 저장소 작업을 수행합니다.
타사 패키지에 대해 이야기한 후 상위 페이지의 js를 작성하는 방법을 살펴보겠습니다.
class crosData { constructor(options) { supportCheck(); this.options = Object.sign({ iframeUrl: '', 만료: '30d' }, this.cid = 0; .iframeBeforeFuns = []; this.parent = window; 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) { accept('불법적 출처!'); return; } if (data.err) { this.cbs[data.cbid].reject(data.err) } else { this.cbs[data.cbid].resolve(data .ret); } this.cbs[data.cbid]; }) } createIframe(url) { addEvent(document, 'domready', () => var 프레임 = document.createElement('iframe'); 프레임.style.cssText = '너비:1px;높이:1px;테두리:0;위치:절대;왼쪽:-9999px;상단:-9999px;'; 'src', url); 프레임.온로드 = () => { this.child = 프레임.콘텐츠윈도우; this.iframeBeforeFuns.forEach(item => item()); } document.body.appendChild(frame); }); } postHandle(type, args) { return new Promise((해결, 거부) => { var cbid = this.cid; var message = { cbid: cbid, 원본: 새 url(location.href).origin, 작업: 유형, 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.sign({ 만료: ms (this.options.expire) }, 옵션); return this.send('set', [key, val, options]) } get(key, options) { 옵션 = Object.sign({ domain: new url(location.href).origin }, options) return this.send('get', [key, options]) }clear(key) { return this.send('clear ', [열쇠]); }}
아마도 몇 가지 방법만 있을 것입니다. 여기에 몇 가지 핵심 사항이 있습니다. 이에 대해 이야기하겠습니다.
1. get, set,clear 메소드는 모두 동일하게 send 메소드라고 부르지만 옵션 부분을 보완합니다.
2. send 메소드는 promise 객체를 반환합니다. iframe이 성공적으로 온로드되면 postHandle 메소드가 직접 호출되어 postMessage 작업이 계속 로드되는 경우 현재 작업이 iframeBeforeFuns 배열로 푸시됩니다. 함수를 호출하고 iframe onload가 끝날 때까지 기다립니다. 통합 호출 후 함수도 postHandle 메서드에 래핑됩니다.
3. postHandle 메소드는 요청을 보내기 전에 데이터를 래핑하고 cbid, Origin, action 및 args를 생성합니다. cbs 개체는 각 cbid 아래에 확인 및 거부를 저장하고 처리 전에 하위 페이지의 postMessage가 반환될 때까지 기다립니다. postMessage는 참조를 유지할 수 없고 함수를 전달할 수 없기 때문에 여기서는 연결을 위해 이 방법을 선택했습니다.
4. 생성자는 이해하기 쉽습니다. 이 클래스가 초기화되면 필요한 몇 가지 옵션 속성을 정의하고 iframe을 만든 다음 메시지 이벤트를 수신하고 하위 페이지에서 반환된 메시지를 처리합니다.
5. 상위 페이지의 메시지 이벤트에서 나에게 전송된 메시지가 내가 연 창 iframe이어야 하는지 확인해야 합니다. 그렇지 않으면 오류가 보고되고 cbs의 해결 및 거부가 다음에 따라 실행됩니다. 데이터의 식별자에 오류가 있습니다.
6. createIframe 메서드에서 iframe onload의 콜백은 생성 전 캐싱 호출 메서드를 처리합니다. 여기서는 본문이 구문 분석되기 전에 SDK가 실행될 수 있으므로 주의하세요.
하위 부분의 코드는 다음과 같습니다.
class iframe { set(key, val, options, Origin) { //val 크기를 확인합니다. 20k를 초과할 수 없습니다. val = val.toString() val = this.lz ? lzstring.compressToUTF16(val); 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, lasttime: Date.now(),expiration: Date.now() + options.expire }; store.set(key, data); //최대 저장 공간보다 큰 경우 마지막 업데이트를 삭제합니다. if (store.size() > this.storemax) { varkeys = store.keys()keys = key.sort( (a, b) => { var item1 = store.get(a), item2 = store.get(b); return item2.lasttime - item1.lasttime; }); this.storemax - store.size()); while (removesize) { store.remove(keys.pop()); deletesize-- } } return { ret: data } } get(key, options) { var message = {}; 키 = store.keys(); var regexp = new RegExp('^' + this.prefix + '_' + 키 + ',' + 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 unundefined } else { //마지막 업데이트; val: data.val, lasttime: Date.now(), 만료: data.expire }); } data.val = this.lz ? lzstring.decompressFromUTF16(data.val) : data.val; filter(item => { return !!item; //필터가 정의되지 않음 }) return message; store.remove(`${this.prefix}_${key},${origin}`); return {}; }clearOtherKey() { //불법 키 삭제 varkeys = store.keys(); new RegExp('^' + this.prefix); keys.forEach(key => { if (!keyReg.test(key)) { store.remove(key); } }); } constructor(safeDomain, lz) { supportCheck(); this.safeDomain = /.*/; this.prefix = '_cros'() 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 = new url( evt .origin).hostname; var 오리진 = 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); message.cbid = cbid; 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. 설정 방법에서는 각 데이터에 대해 크기 확인 및 lz 압축이 수행됩니다. 저장된 데이터에는 val, key, 만료 시간 및 업데이트 시간(LRU 계산에 사용됨)이 포함됩니다.
5. set 방식에서는 저장된 ls 개수가 최대 한도를 초과하는 경우 이때 삭제 작업이 필요하다. LRU는 Least Recent Used의 약자, 즉 가장 최근에 사용된 것이다. 모든 키 값을 순회하고, 키 값을 정렬하고, 마지막 시간을 통과한 다음 키 배열의 팝 작업을 수행하여 스택 끝에서 지워야 할 키를 가져온 다음 하나씩 삭제합니다. .
6. get 메소드에서는 모든 키 값을 탐색하고, 가져와야 하는 도메인의 키를 일치시킨 다음 반환 값에서 키를 분해합니다(키와 도메인 형식으로 저장합니다). 여러 개의 일치하는 값이 반환되어야 합니다. 만료된 데이터에 대해 최종 필터를 만든 다음 lz를 사용하여 val 값의 압축을 풀어 사용자가 올바른 결과를 얻도록 합니다.
위의 내용은 전반적인 구현 코딩 프로세스 및 검토 내용입니다.
5. 직면한 몇 가지 함정위에는 주요 코드만 제시되어 있기 때문에 완전한 코드는 아닙니다. 로직 자체가 비교적 명확하기 때문에 짧은 시간 안에 작성할 수 있습니다. 아래에서 함정에 대해 이야기해 보겠습니다.
1. 로컬스토리지의 스토리지 가치를 계산합니다.
우리 모두는 5MB 제한이 있다는 것을 알고 있으므로 각 데이터 조각의 최대 요구 사항은 20*1024바이트를 초과할 수 없습니다. 바이트 계산을 위해 localstorage는 변환을 위해 utf16 인코딩을 사용해야 합니다. JS 문자 계산을 참조하세요. 문자열 섹션 수로 점유
2. 호환성
IE8에서는 postMessage에 문자열을 전달하는 것이 가장 좋습니다. 이벤트를 다듬고 JSON을 다듬어야 합니다.
3. iframe 생성 시 비동기 처리
여기서는 이전에 setTimeout을 재귀적으로 대기했지만 나중에는 위의 구현 방법으로 변경했습니다. onload 이후에는 promise API의 통합을 보장하기 위해 promise의 reslove가 균일하게 처리됩니다.
4. 데이터를 저장할 때 공간 복잡도와 시간 복잡도가 비교됩니다.
첫 번째 버전은 위의 구현이 아니며 3가지 버전을 구현했습니다.
첫 번째 버전은 시간 복잡도를 줄이기 위해 LRU 배열을 저장하지만 공간 복잡도를 낭비합니다. 또한 테스트 후 저장소의 get 메서드는 주로 구문 분석에 시간이 많이 걸리기 때문에 상대적으로 시간이 많이 걸립니다.
두 번째 버전에서는 lz-string의 압축률을 극대화하기 위해 LRU 배열을 포함한 모든 데이터를 키 값에 저장했습니다. 그 결과 lz-string 및 getItem이 있는 경우 구문 분석 시간 소모가 매우 큽니다. 많은 데이터가 계산되지만 시간 복잡도는 가장 낮습니다.
마지막 버전은 위 버전입니다. 시간 복잡도와 공간 복잡도를 어느 정도 희생했지만, 병목 현상이 설정 및 가져오기의 읽기 및 쓰기 속도에 있기 때문에 단일 저장의 읽기 및 쓰기 속도가 매우 빠릅니다. 키는 로컬 스토리지의 경우 맨 아래 레이어가 사용되기 때문에 성능이 여전히 매우 좋습니다. 20kb에 100개의 항목을 저장할 수 있으며 읽기 및 쓰기 시간은 약 1초입니다.
6. 요약 및 비교모듈을 작성한 후 다음과 같은 라이브러리가 있다는 것을 깨달았습니다. zendesk/cross-storage
하지만 그의 API와 소스 코드를 확인하고 구현 방법을 비교한 결과 내 버전이 더 중요하다고 생각했습니다.
1. 내 버전에서는 도메인 이름과 데이터 관리를 제어할 수 있습니다.
2. 내 버전의 promise API는 onConnect가 하나 더 적기 때문에 더 단순화되었습니다. 그의 구현을 참조할 수 있습니다. 이는 내가 작성한 것보다 훨씬 더 많은 기능을 제공하며 비동기를 기다리는 iframe 문제를 해결하지 못합니다.
3. LZ 압축 데이터는 지원되지 않습니다.
4. LRU 스토리지 풀 관리가 지원되지 않아 스토리지가 너무 많아 쓰기 실패가 발생할 수 있습니다.
5. 그는 모든 상호 작용에 대해 iframe을 만드는 것 같은데, 이는 DOM 작업과 브로드캐스팅의 낭비입니다. 물론 그는 여러 클라이언트에 연결해야 할 수도 있으므로 이렇게 처리합니다. .
요약위 내용은 도메인 간 데이터 공유를 실현하기 위해 쿠키 대신 로컬 저장소를 사용하는 문제에 대한 편집자의 소개입니다. 질문이 있는 경우 메시지를 남겨주시면 편집자가 답변해 드리겠습니다. 시간에. 또한 VeVb 무술 웹사이트를 지원해 주신 모든 분들께 감사드립니다!