当前位置: 首页 > 科技观察

探秘前端黑科技——通过png图片的rgba值缓存数据

时间:2023-03-17 00:40:44 科技观察

说到前端缓存,大部分人都会想到几种常规方案,比如cookie、localStorage、sessionStorage,或者indexedDB和webSQL,以及清单离线缓存。另外,有没有其他办法在前端缓存数据?本文将带大家通过png图片的rgba值一步步探索如何缓存数据。原理我们知道,通过为静态资源设置Cache-Control和Expires响应头,可以强制浏览器对其进行缓存。当浏览器向后台发起请求时,会先到自己的缓存中查找,如果缓存中没有,就会继续向服务器请求静态资源。利用这一点,我们可以通过这种静态资源缓存机制存储一些需要缓存的信息。那么我们如何将信息写入静态资源呢?Canvas提供了.getImageData()方法和.createImageData()方法,分别用于读取和设置图像的rgba值。所以我们可以使用这两个API来读写信息。接下来看示意图:当静态资源进入缓存后,后续任何对图片的请求都会先去本地缓存中查找,也就是说信息实际上已经以图片的形式缓存在本地了。注意,由于rgba值只能是[0,255之间的整数],所以本文讨论的方法只适用于纯数字组成的数据。静态服务器我们使用node搭构建一个简单的静态服务器:constfs=require('fs')consthttp=require('http')consturl=require('url')constquerystring=require('querystring')constutil=require('util')constserver=http.createServer((req,res)=>{letpathname=url.parse(req.url).pathnameletrealPath='assets'+pathnameconsole.log(realPath)if(realPath!=='assets/upload'){fs.readFile(realPath,"binary",function(err,file){if(err){res.writeHead(500,{'Content-Type':'text/plain'})res.end(err)}else{res.writeHead(200,{'Access-Control-Allow-Origin':'*','Content-Type':'image/png','ETag':"666666",'Cache-Control':'public,max-age=31536000','Expires':'Mon,07Sep202609:32:27GMT'})res.write(file,"binary")res.end()}})}else{letpost=''req.on('data',(chunk)=>{post+=chunk})req.on('end',()=>{post=querystring.parse(post)console.log(post.imgData)res.writeHead(200,{'Access-Control-Allow-Origin':'*'})letbase64Data=post.imgData.replace(/^data:image\/\w+;base64,/,"")letdataBuffer=newBuffer(base64Data,'base64')fs.writeFile('assets/out.png',dataBuffer,(err)=>{if(err){res.write(err)res.end()}res.write('OK')res.end()})})}})server.listen(80)console.log('Listeningonport:80')这个静态资源的功能很简单,它提供了两个功能:通过client客户端发送的base64生成图片并保存到服务器;设置图片缓存时间并发送给客户端的关键部分是设置响应头:res.writeHead(200,{'Access-Control-Allow-Origin':'*','Content-Type':'image/png','ETag':"666666",'Cache-Control':'public,max-age=31536000','Expires':'Mon,07Sep202609:32:27GMT'})我们为这张图片设置了一年的Content-Type和十年的Expires,理论上足够长了。接下来,让我们进行客户端的编码。Client假设我们需要存储32位的数据,所以我们设置宽度为8,高度为1。为什么32位数据对应长度为8,是因为每个像素点都有一个rgba对应红绿蓝alpha这4个值,所以需要除以4。letkeyString='01234567890123456789012345678901'letcanvas=document.querySelector('#canvas')letctx=canvas.getContext('2d')letimgData=ctx.createImageData(8,1)for(leti=0;i{if(data==='OK'){letimg=newImage()img.crossOrigin="anonymous"img.src='http://xx.xx.xx.xx:80/out.png'img.onload=()=>{console.log('完成图片请求和缓存')ctx.drawImage(img,0,0)console.log(ctx.getImageData(0,0,8,1).data)}}})代码很简单。base64通过.toDataURL()方法发送给服务端,服务端处理后生成图片返回。图片资源地址为http://xx.xx.xx.xx:80/out.png。img.onload后,图片已经缓存到本地了。我们打印出这个事件中的图像信息作为与源数据的对比。结果分析打开服务端,运行客户端,第一次加载时可以通过控制台看到响应图片信息:200OK,证明图片是从服务端获取的。关闭当前页面,重新加载:200OK(fromcache),证明图片是从本地缓存中读取的。接下来直接看rgba值的对比:源数据:[50,101,152,203,54,105,156,207,58,109,150,201,52,103,154,205,56,107,158,209,50,101,152,203,54,105,156,207,58,109,150,201]缓存数据:[50,100,152,245,54,105,157,246,57,109,149,244,52,103,154,245,56,107,157,247,50,100,152,245,54,105,157,246,57,109,149,244]可以可以看出源数据与缓存数据基本一致,alpha值误差偏大,rgb值偶有误差。通过分析,认为错误的原因是服务端base64转buffer过程中涉及的计算会导致数据发生变化,需要验证。根据前面的结论,经过验证确定源数据和缓存数据有误的原因是alpha值的干扰。如果我们直接把alpha值设置为255,只把rgb值里面的数据存进去,就可以消除这个错误。以下是改进后的结果:源数据:[0,1,2,255,4,5,6,255,8,9,0,255,2,3,4,255,6,7,8,255,0,1,2,255,4,5,6,255,8,9,0,255]缓存数据:[0,1,2,255,4,5,6,255,8,9,0,255,2,3,4,255,6,7,8,255,0,1,2,255,4,5,6,255,8,9,0,255]因为我比较懒,只是把alpha值设置为255,并没有更新循环赋值的逻辑,所以第4n个元数据直接替换为255,保留读者可以有时间自己修改吧。。。综上所述,这种利用png图片的rgba值来缓存数据的黑科技,理论上是可行的,但在实际运行过程中,可能需要考虑更多的影响因素。比如尽量在服务器端排除错误,采用容错机制等,其实是可以做到的。值得注意的是localhost默认可能直接通过本地而不是服务器请求资源,所以在本地实验中可以设置header进行cors跨域,设置IP地址和80端口模拟服务器访问.后记说是黑科技,其实原理很简单。与之类似的还有通过Etag等方式实现的强缓存。研究目的仅供学习,不得用于非法用途。如果读者发现本文有错误或疏漏之处,欢迎指正,也希望有兴趣的朋友一起讨论。