天天看點

探索前端黑科技——通過png圖的rgba值緩存資料

說起前端緩存,大部分人想到的無非是幾個正常的方案,比如cookie,localstorage,sessionstorage,或者加上indexeddb和websql,以及manifest離線緩存。除此之外,到底還有沒有别的方法可以進行前端的資料緩存呢?這篇文章将會帶你一起來探索,如何一步一步地通過png圖的rgba值來緩存資料的黑科技之旅。

原理

我們知道,通過為靜态資源設定cache-control和expires響應頭,可以迫使浏覽器對其進行緩存。浏覽器在向背景發起請求的時候,會先在自身的緩存裡面找,如果緩存裡面沒有,才會繼續向伺服器請求這個靜态資源。利用這一點,我們可以把一些需要被緩存的資訊通過這個靜态資源緩存機制來進行儲存。

那麼我們如何把資訊寫入到靜态資源中呢?canvas提供了.getimagedata()方法和.createimagedata()方法,可以分别用于讀取和設定圖檔的rgba值。是以我們可以利用這兩個api進行資訊的讀寫操作。

接下來看原理圖:

探索前端黑科技——通過png圖的rgba值緩存資料

當靜态資源進入緩存,以後的任何對于該圖檔的請求都會先查找本地緩存,也就是說資訊其實已經以圖檔的形式被緩存到本地了。

注意,由于rgba值隻能是[0, 255]之間的整數,是以本文所讨論的方法僅适用于純數字組成的資料。

靜态伺服器

我們使用node搭建一個簡單的靜态伺服器:

const fs = require('fs') 

const http = require('http') 

const url = require('url') 

const querystring = require('querystring') 

const util = require('util') 

const server = http.createserver((req, res) => { 

  let pathname = url.parse(req.url).pathname 

  let realpath = 'assets' + pathname 

  console.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, 07 sep 2026 09:32:27 gmt' 

        }) 

        res.write(file, "binary") 

        res.end() 

      } 

   }) 

  } else { 

    let post = '' 

    req.on('data', (chunk) => { 

      post += chunk 

    }) 

    req.on('end', () => { 

      post = querystring.parse(post) 

      console.log(post.imgdata) 

      res.writehead(200, { 

        'access-control-allow-origin': '*' 

      }) 

      let base64data = post.imgdata.replace(/^data:image\/\w+;base64,/, "") 

      let databuffer = new buffer(base64data, 'base64') 

      fs.writefile('assets/out.png', databuffer, (err) => { 

        if (err) { 

          res.write(err) 

          res.end() 

        } 

        res.write('ok') 

  } 

}) 

server.listen(80) 

console.log('listening on port: 80')  

這個靜态資源的功能很簡單,它提供了兩個功能:通過用戶端傳來的base64生成圖檔并儲存到伺服器;設定圖檔的緩存時間并發送到用戶端。

關鍵部分是設定響應頭:

res.writehead(200, { 

  'access-control-allow-origin': '*', 

  'content-type': 'image/png', 

  'etag': "666666", 

  'cache-control': 'public, max-age=31536000', 

  'expires': 'mon, 07 sep 2026 09:32:27 gmt' 

})  

我們為這張圖檔設定了一年的content-type和十年的expires,理論上足夠長了。下面我們來進行用戶端的coding。

用戶端

<!-- client.html --> 

<canvas id="canvas" width="8", height="1"></canvas>  

假設我們需要存儲的是32位的資料,是以我們為canvas設定寬度為8,高度為1。到底為什麼32位資料對應長度為8,是因為每一個像素都有一個rgba,對應着red,green,blue和alpha4個數值,是以需要除以4。

<!-- client.js --> 

let keystring = '01234567890123456789012345678901' 

let canvas = document.queryselector('#canvas') 

let ctx = canvas.getcontext('2d') 

let imgdata = ctx.createimagedata(8, 1) 

for (let i = 0; i < imgdata.data.length; i += 4) { 

    imgdata.data[i + 0] = parseint(keystring[i]) + 50 

    imgdata.data[i + 1] = parseint(keystring[i + 1]) + 100 

    imgdata.data[i + 2] = parseint(keystring[i + 2]) + 150 

    imgdata.data[i + 3] = parseint(keystring[i + 3]) + 200 

ctx.putimagedata(imgdata, 0, 0)  

首先我們假設需要被緩存的字元串為32位的01234567890123456789012345678901,然後我們使用.createimagedata(8,

1)生成一個空白的imgdata對象。接下來,我們對這個空對象進行指派。為了實驗效果更加直覺,我們對rgba值都進行了放大。設定完了imgdata以後,通過.putimagedata()方法把它放入我們的canvas即可。

我們現在可以列印一下,看看這個imgdata是什麼:

// console.log(imgdata.data) 

[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]  

接下來,我們要把這個canvas編譯為一張圖檔的base64并發送給伺服器,同時接收伺服器的響應,對圖檔進行緩存:

$.post('http://xx.xx.xx.xx:80/upload', { imgdata: canvas.todataurl() }, (data) => { 

    if (data === 'ok') { 

        let img = new image() 

        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) 

    } 

代碼很簡單,通過.todataurl()方法把base64發送到伺服器,伺服器處理後生成圖檔并傳回,其圖檔資源位址為http://xx.xx.xx.xx:80/out.png。在img.onload後,其實圖檔就已經完成了本地緩存了,我們在這個事件當中把圖檔資訊列印出來,作為和源資料的對比。

結果分析

開啟伺服器,運作用戶端,第一次加載的時候通過控制台可以看到響應的圖檔資訊:

探索前端黑科技——通過png圖的rgba值緩存資料

200 ok,證明是從服務端擷取的圖檔。

關閉目前頁面,重新載入:

探索前端黑科技——通過png圖的rgba值緩存資料

200 ok (from cache),證明是從本地緩存讀取的圖檔。

接下來直接看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值的幹擾所緻。如果我們把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等方法進行強緩存。研究的目的僅僅為了學習,千萬不要作為非法之用。如果讀者們發現這篇文章有什麼錯漏之處,歡迎指正,也希望有興趣的朋友可以一起進行讨論。

作者:佚名

來源:51cto