探索前端黑科技——經過 png 圖的 rgba 值緩存數據

寫於 2016.09.09html

本文系原創,歡迎轉載,轉載請註明做者信息 項目地址:SphinxJS 在線體驗地址:jrainlau.github.io/sphinx/前端

提及前端緩存,大部分人想到的無非是幾個常規的方案,好比cookielocalStoragesessionStorage,或者加上indexedDBwebSQL,以及manifest離線緩存。除此以外,到底還有沒有別的方法能夠進行前端的數據緩存呢?這篇文章將會帶你一塊兒來探索,如何一步一步地經過png圖的rgba值來緩存數據的黑科技之旅。node

PS:本文所研究的內容已經整合成一個開源的JS庫,名字叫SphinxJS,感興趣的同窗能夠移步到這篇文章SphinxJS——把字符串編碼成png圖片的超輕量級開源庫去看相關的文檔,歡迎STAR!git

原理

咱們知道,經過爲靜態資源設置Cache-ControlExpires響應頭,能夠迫使瀏覽器對其進行緩存。瀏覽器在向後臺發起請求的時候,會先在自身的緩存裏面找,若是緩存裏面沒有,纔會繼續向服務器請求這個靜態資源。利用這一點,咱們能夠把一些須要被緩存的信息經過這個靜態資源緩存機制來進行儲存。github

那麼咱們如何把信息寫入到靜態資源中呢?canvas提供了.getImageData()方法和.createImageData()方法,能夠分別用於讀取設置圖片的rgba值。因此咱們能夠利用這兩個API進行信息的讀寫操做。web

接下來看原理圖:canvas

當靜態資源進入緩存,之後的任何對於該圖片的請求都會先查找本地緩存,也就是說信息其實已經以圖片的形式被緩存到本地了。跨域

注意,因爲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')
        res.end()
      })
    })
  }
})

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,對應着redgreenbluealpha4個數值,因此須要除以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後,其實圖片就已經完成了本地緩存了,咱們在這個事件當中把圖片信息打印出來,做爲和源數據的對比。

結果分析

開啓服務器,運行客戶端,第一次加載的時候經過控制檯能夠看到響應的圖片信息:

200 OK,證實是從服務端獲取的圖片。

關閉當前頁面,從新載入:

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值的偏差偏大,在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等方法進行強緩存。研究的目的僅僅爲了學習,千萬不要做爲非法之用。若是讀者們發現這篇文章有什麼錯漏之處,歡迎指正,也但願有興趣的朋友能夠一塊兒進行討論。

感謝你的閱讀。我是Jrain,歡迎關注個人專欄,將不按期分享本身的學習體驗,開發心得,搬運牆外的乾貨。下次見啦!

相關文章
相關標籤/搜索