返回顶部
首页 > 资讯 > 精选 >怎么在使用localstorage代替cookie实现跨域共享数据
  • 884
分享到

怎么在使用localstorage代替cookie实现跨域共享数据

2023-06-09 21:06:58 884人浏览 安东尼
摘要

这篇文章给大家介绍怎么在使用localstorage代替cookie实现跨域共享数据,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一,背景 因为网站系统的日益庞大,不同域名业务,甚至不同合作方网站的cooki

这篇文章给大家介绍怎么在使用localstorage代替cookie实现跨域共享数据,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

一,背景

 因为网站系统的日益庞大,不同域名业务,甚至不同合作方网站的cookie可能或多或少需要进行共享使用,遇到这个情况的时候,大家一般想到的是使用登录中心分发cookie状态再进行同步进行解决,成本较高而且实施起来比较复杂和麻烦。

因为cookie在跨域的情况下,浏览器根本不允许互相访问的限制,为了突破这个限制,所以有了以下这个实现方案,使用postmessage和localstorage进行数据跨域共享。

二,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信息会带着domain信息,不填写则返回本域的}).then((vals)=>{    console.log(val) //异步获取存储数据,可能多个,是个数组 [{},{}]}).catch((err)=>{});store.clear('key').then().catch(); //只清楚当前域下的key,不允许清除其他域下的key,只能读

一个模块上手快不快主要看api,所以对于一个数据共享模块,我认为支持set,get,clear这3个方法就ok了,因为postmessage本身是个一来一回的异步的行为,包装成promise的肯定更为合适和易用。因为localstorage不支持过期时间,所以需要一个全局的过期时间配置,当然也可以在set的时候进行单独配置,而get的时候我们可以指定获取某个域下的数据或者多个域下的数据,因为key名可能重复,但是域只有一个。这里就牵扯到了数据的管理,后边单独来说,最后clear和set的api只能种本域的数据,不可以操作其他域下的数据,get被允许。

下面我们看一下,client端的设置和API:

<!DOCTYPE html><html>    <head>        <meta charset="utf-8">        <title>crosData</title>     </head>    <body>        <script>            window.CROS = {                domain:/(.*).sina.cn/, //或者你允许的域名,支持正则和*通配符                lz:false //是否开启lz压缩val字符            };        </script>        <script src="Http://cdn/sdk.js"></script>    </body></html>

你可以灵活在任何一个域下的一个html文档中,引入client的js sdk,然后通过全局属性的方式配置一个你允许被种到这个文档所在域下的domain白名单,支持正则,然后lz是是否启动lz-string压缩,至于什么是lz压缩后边我再介绍。

到这里,一个比较通用的API设计就完成了,下面我们看一下实现原理和具体的一些问题。

三,实现原理

说起来好想蛮简单的,但是写起来其实并不是,我们首先需要知道postMessage怎么用,这个属于很常见的一个API了,他有一个要点这里告诉大家,就是postMessage只能在iframe中或者使用window.open这种开启新页面的方式进行互相通讯,当然这里我们首先就要创建一个隐藏的iframe,进行跨域。

怎么在使用localstorage代替cookie实现跨域共享数据 

懒得拿工具画图了,因为流程比较清晰,这里拿文字复述一下整个通讯流程,首先父页面创建一个隐藏的iframe,然后当执行set,get,clear等command的时候,通过postMessage来进行消息广播,子页面接收到消息后,解析命令,数据和回调id(postMessage无法传递函数和引用,兼容问题导致,最好只传string类型,所以还需要对data做stringify)。然后当子页面处理完localstorage的操作后,再通过postMessage把对应的cbid和data返回给父页面,父页面监听message事件,处理结果。

四,编码

嗯,所以说没几行,我们下面开始进行编码了:

首先介绍一下我们用到的第三方包都有什么,为什么要用:

1,url-parse 对url进行parse解析,主要用他里面的origin属性,因为postMessage本身对origin就有严格的验证,我们要支持白名单和域名管理也需要。

2,ms 对时间简写做毫秒转换的工具库。

3, lz-string 对字符串做压缩用的工具包,这里给大家科普一下LZ压缩算法,首先了解LZ需要先了解RLZ,Run Length Encoding ,是一个针对无损压缩的非常简单的算法。它用重复字节和重复的次数来简单描述来代替重复的字节。LZ 压缩算法的背后是使用 RLE 算法用先前出现的相同字节序列的引用来替代。简单的讲, LZ 算法被认为是字符串匹配的算法。例如:在一段文本中某字符串经常出现,并且可以通过前面文本中出现的字符串指针来表示。

lz-string本身有优势就是可以大大的减小你的储存量,本身5MB的localstorage如果用来支持多域名的数据保存,很快就会被压缩用完,但是lz-string本身比较慢,消耗比较大,大家平时在工作中如果对传输数据量有大小要求的话可以尝试使用这个压缩算法来优化字符串长度,默认是不开启的。

4,store2 本身localstorage的api比较简陋,为了减少代码逻辑复杂度,这里选了一个比较流行的localstorage的实现库来进行store的操作。

说完了第三方包我们下面看一下父页面的js怎么来写:

class crosData {  constructor(options) {    supportCheck();    this.options = Object.assign({      iframeUrl: '',      expire: '30d'    }, options);    this.cid = 0;    this.cbs = {};    this.iframeBeforeFuns = [];    this.parent = window;    this.origin = new 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的message,其他的都是不合法的,直接报错      if (origin !== this.origin) {        reject('illegal origin!');        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 = 'width:1px;height:1px;border:0;position:absolute;left:-9999px;top:-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, reject) => {      var cbid = this.cid;      var message = {        cbid: cbid,        origin: new url(location.href).origin,        action: type,        args: args      }      this.child.postMessage(JSON.stringify(message), this.origin);      this.cbs[cbid] = {        resolve,        reject      }      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({      domain: new url(location.href).origin    }, options);    return this.send('get', [key, options]);  }  clear(key) {    return this.send('clear', [key]);  }}

大概方法就这么几个,这里有几个关键点,我说一下。

1,get,set,clear方法都是统一的调用的send方法,只不过对options部分做了补齐。

2,send方法返回一个promise对象,如果iframe已经onload成功,则直接调用postHandle方法进行postMessage操作,如果iframe还在加载中,则把当前的操作推到iframeBeforeFuns数组中,用函数包裹,等待iframe onload结束后统一调用,函数包裹的也是postHandle方法。

3,postHandle方法,在发送请求前包装data,生成cbid,origin,action和args,cbs对象保存了每个cbid下的resolve和reject,等待子页面的postMessage返回后处理。因为postMessage不能保留引用,不能传函数,所以这里选择这个方法来进行关联。

4,constructor比较好理解,当这个类被初始化的时候,我们定义了我们需要的一些options的属性,创建iframe,然后监听message事件,处理子页面返回的消息。

5,在父页面的message事件中,我们要校验,给我发消息的必须是我打开的这个窗口iframe,否则报错,然后根据data中的err标识来让cbs中的resolve和reject进行执行。

6,createIframe方法中,iframe onload中的回调处理创建前 缓存的调用方法,这里注意使用了domready,因为可能body还没解析就会进行sdk的执行。

下面是child部分的代码:

class iframe {  set(key, val, options, origin) {    //检查val大小,不能超过20k.    val = val.toString();    val = this.lz ? lzstring.compressToUTF16(val) : val;    var valsize = sizeof(val, 'utf16'); //localStorage 储存使用utf16编码计算字节    if (valsize > this.maxsize) {      return {        err: 'your store value : "' + valstr + '" size is ' + valsize + 'b, maxsize :' + this.maxsize + 'b , use utf16'      }    }    key = `${this.prefix}_${key},${new url(origin).origin}`;    var data = {      val: val,      lasttime: Date.now(),      expire: Date.now() + options.expire    };    store.set(key, data);    //大于最大储存个数,删除最后一次更新的    if (store.size() > this.storemax) {      var keys = store.keys();      keys = keys.sort((a, b) => {        var item1 = store.get(a),          item2 = store.get(b);        return item2.lasttime - item1.lasttime;      });      var removesize = Math.abs(this.storemax - store.size());      while (removesize) {        store.remove(keys.pop());        removesize--;      }    }    return {      ret: data    }  }  get(key, options) {    var message = {};    var keys = store.keys();    var regexp = new RegExp('^' + this.prefix + '_' + key + ',' + options.domain + '$');    message.ret = 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 {        //更新lasttime;        store.set(storeKey, {          val: data.val,          lasttime: Date.now(),          expire: data.expire        });      }      data.val = this.lz ? lzstring.decompressFromUTF16(data.val) : data.val;      return data;    }).filter(item => {      return !!item; //过滤undefined    });    return message;  }  clear(key, origin) {    store.remove(`${this.prefix}_${key},${origin}`);    return {};  }  clearOtherKey() {    //删除不合法的key     var keys = store.keys();    var keyReg = new RegExp('^' + this.prefix);    keys.forEach(key => {      if (!keyReg.test(key)) {        store.remove(key);      }    });  }  constructor(safeDomain, lz) {    supportCheck();    this.safeDomain = safeDomain || /.*/;    this.prefix = '_cros';    this.clearOtherKey();    if (Object.prototype.toString.call(this.safeDomain) !== '[object RegExp]') {      throw new Error('safeDomain must be regexp');    }    this.lz = lz;    this.storemax = 100;    this.maxsize = 20 * 1024; //字节    addEvent(window, 'message', (evt) => {      var data = JSON.parse(evt.data);      var originHostName = new 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);          message.cbid = cbid;          window.top.postMessage(JSON.stringify(message), origin);        }      } else {        window.top.postMessage(JSON.stringify({          cbid: cbid,          err: 'Illegal domain'        }), origin);      }    });  }}

代码也不多,这里简单说一下各个方法的用处和组织关系:

1,constructor部分,上面的类里也进行浏览器特性支持检查,然后定义了store的prefix值,最大个数和每一个key的maxsize等属性。然后我们创建message通道,等待父页面调用。

2,在message中,我们对发送广播的origin进行检查,然后对调用的方法进行检查,调用对应的set,get,clear方法,然后把执行的结果拿到,绑定cbid,最后再postMessage发送回父页面。

3,clearOtherKey 删除不合法的一些store数据,只保留符合格式的数据。

4,set方法中对每一条的数据做size校验,lz压缩,保存的data中包含了val,key,过期时间以及更新时间(用于LRU计算)。

5,set方法中,如果储存的ls个数超过了最大限制,这个时候需要进行删除操作, LRU是Least Recently Used的缩写,即最近最少使用。我们通过遍历所有的key值,对key值做一个排序,通过lasttime,然后进行keys数组的pop操作,拿到堆栈尾部的需要被清除的key,然后逐个删除。

6,get方法中,我们通过遍历所有的key值,匹配到我们需要拿到的domain的域的key,然后把返回值中的key进行拆解(我们储存时是 key,domain的格式),因为api要求返回多个符合的值,我们对过期的数据最后再做一个filter,然后使用lz解压缩val值,保证用户拿到的是正确结果。

以上就是我们的一个整体实现编码过程和review,下面说一说遇到的坑。

五,一些遇到的坑

因为上面只给了主代码,并不是完整代码,因为本身逻辑比较清晰,花一点时间都可以写出来的。下面说说有什么坑的地方。

1,计算localstorage的储存值。

因为我们都知道有5MB的限制,所以每一条数据最大要求不能超过20*1024 字节,对于字节的计算,localstorage要使用utf16的编码进行转换,参考这篇文章: JS计算字符串所占字节数

2,兼容性

ie8下postMessage最好都传字符串,事件需要抹平处理,JSON需要抹平处理。

3,创建iframe时的异步处理

这里之前做了个一个setTimeout的递归等待,后来更改成了上面的实现方法,通过onload后统一处理promise的reslove,保证promise api的统一。

4,数据保存时,空间复杂度 vs 时间复杂度。

第一个版本并不是上面的实现,我实现了3个版本:

第一个版本是保存了一个LRU的数组,为了减少时间复杂度,但是浪费了空间复杂度,而且经过测试,store的get方法耗时比较大,主要是parse的耗时。

第二个版本,为了能让lz-string压缩率最大化,我把所有的数据包括LRU数组保存到了一个key值上,导致数据多的时候lz-string和getItem,parse时间消耗非常大,虽然计算的时间复杂度是最低。

最后一个版本,就是上面的,我牺牲了一些时间复杂度和空间复杂度,但是因为瓶颈在于set和get的读写速度,单个的保存读写速度极快,获取keys的方法因为底层是用的for in localstorage实现的,性能还是很不错的,20kb存满100条,读写也在1s左右,性能非常不错。

六,总结和对比

模块写完了,我才知道原来还有这么一个库: zendesk/cross-storage

但是我查看了他的api和源代码,对比了一下实现方法,我觉得还是我这个版本考虑的比较多。

1,我的版本对域名和数据的管理有控制。

2,我的版本promise api更简化,比它少一个onConnect,可以参考他的实现,比我写的多多了,也没解决这个iframe等待异步的问题。

3,不支持lz压缩数据。

4,不支持LRU的储存池管理,所以可能存多了造成写不进的问题。

5,他貌似每次交互都搞一个iframe,太浪费dom操作和广播了,我觉得一直开着并没有什么问题,当然他可能有需求连接多个client才这么处理的。

关于怎么在使用localstorage代替cookie实现跨域共享数据就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

--结束END--

本文标题: 怎么在使用localstorage代替cookie实现跨域共享数据

本文链接: https://lsjlt.com/news/257887.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 怎么在使用localstorage代替cookie实现跨域共享数据
    这篇文章给大家介绍怎么在使用localstorage代替cookie实现跨域共享数据,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一,背景 因为网站系统的日益庞大,不同域名业务,甚至不同合作方网站的cooki...
    99+
    2023-06-09
  • 利用 PHP Session 跨域实现数据共享
    在开发Web应用程序时,我们经常需要在不同的域名之间共享数据。虽然现在有许多方法可以实现跨域数据共享,但使用PHP Session是一种简单有效的方式。本文将介绍如何利用PHP Session跨域实现数据共享,并提供具体的代码示例。一、PH...
    99+
    2023-10-21
    PHP Session 数据共享 跨域
  • JavaScript中怎么实现跨域共享
    这篇文章将为大家详细讲解有关JavaScript中怎么实现跨域共享,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。同源策略在客户端编程语言中,如javascr...
    99+
    2024-04-02
  • JSONP 入门:轻松实现跨域数据共享
    JSONP(JSON with Padding)是一种跨域请求数据的技术,它允许浏览器从其他域名的服务器请求数据,并通过回调函数来处理响应数据。与传统的 Ajax 技术相比,JSONP 更加简单易用,不需要复杂的配置和处理跨域错误,因此非...
    99+
    2024-02-24
    JSONP Ajax 跨域 数据共享
  • android跨进程共享数据怎么实现
    Android中跨进程共享数据可以使用以下方法实现:1. 使用Binder机制:Binder是Android中一种轻量级的进程间通信...
    99+
    2023-10-10
    android
  • SpringMVC域对象共享数据怎么实现
    本文小编为大家详细介绍“SpringMVC域对象共享数据怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“SpringMVC域对象共享数据怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。SpringM...
    99+
    2023-06-30
  • 怎么用kbone实现跨页面通信和跨页面数据共享
    本篇内容介绍了“怎么用kbone实现跨页面通信和跨页面数据共享”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成...
    99+
    2024-04-02
  • nginx怎么使用ctx实现数据共享
    这篇文章主要介绍了nginx怎么使用ctx实现数据共享的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇nginx怎么使用ctx实现数据共享文章都会有所收获,下面我们一起来看看吧。...
    99+
    2024-04-02
  • 利用PHP trait DTO实现跨平台数据共享
    随着互联网的迅速发展,跨平台数据共享已经成为许多项目中的一个重要需求。为了在不同平台上共享数据,我们可以使用PHP的trait技术来实现。本文将介绍如何使用trait DTO(Data Transfer Object)来实现跨平台数据共享,...
    99+
    2023-10-21
    PHP trait DTO (data transfer object) 跨平台数据共享
  • 怎么使用JavaScript cookie跨域访问实现广告推广
    本文小编为大家详细介绍“怎么使用JavaScript cookie跨域访问实现广告推广”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么使用JavaScript cookie跨域访问实现广告推广”文章能帮助大家解决疑惑,下面跟着小编的思路...
    99+
    2023-07-04
  • SpringMVC请求域对象的数据共享怎么实现
    本篇内容主要讲解“SpringMVC请求域对象的数据共享怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“SpringMVC请求域对象的数据共享怎么实现”吧!SpringMVC支持路径中的占...
    99+
    2023-06-29
  • React数据共享useContext怎么实现
    这篇“React数据共享useContext怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“React数据共享useC...
    99+
    2023-06-30
  • Ubuntu中怎么实现数据共享
    本篇文章给大家分享的是有关Ubuntu中怎么实现数据共享,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Netweaver通过FM RFC_TABLE_ACCESS在系统间共享数...
    99+
    2023-06-03
  • php怎么实现不同域名之间的数据共享
    本篇内容主要讲解“php怎么实现不同域名之间的数据共享”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“php怎么实现不同域名之间的数据共享”吧!一、前端跨域在当前的Web开发环境中,“跨域”是一个...
    99+
    2023-07-05
  • 使用iframe怎么实现跨域
    使用iframe怎么实现跨域?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。为什么会跨域为了保证用户信息的安全,95年的时候Netscape公司引进了同源策略,里面的同源指的是三...
    99+
    2023-06-09
  • 使用postMessage怎么实现iframe跨域
    今天就跟大家聊聊有关使用postMessage怎么实现iframe跨域,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。postMessage是什么此处引用MDN关于postMessag...
    99+
    2023-06-09
  • 微信小程序怎么实现数据共享与方法共享
    微信小程序怎么实现数据共享与方法共享,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。全局数据共享 Mobox原生小程序开发中我们可以通过 mobx-miniprogram 配...
    99+
    2023-06-26
  • 使用Java怎么操作共享数据
    这篇文章将为大家详细讲解有关使用Java怎么操作共享数据,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;3...
    99+
    2023-06-14
  • python多线程数据共享怎么实现
    在Python中,可以使用`threading`模块来实现多线程数据共享。具体步骤如下: 导入`threading`模块:`im...
    99+
    2023-10-26
    python
  • 使用Ajax怎么实现跨域访问
    使用Ajax怎么实现跨域访问?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。解决方案1.JsonpJsonp解决跨域相对简单,服务器无需任何配置。具体实现如下:$.ajax({&...
    99+
    2023-06-08
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作