Exploring front-end black technology — caching data through RGBA value of PNG graph

Time:2021-7-25

This article is original, welcome to reprint, please indicate the author’s information
Project address:SphinxJS
Online experience address:https://jrainlau.github.io/sp…

When it comes to front-end caching, most people think of nothing more than several conventional schemes, such ascookielocalStoragesessionStorageOr addindexedDBandwebSQL, andmanifestOffline cache. In addition, is there any other way to cache the front-end data? This article will take you to explore the black technology journey of how to cache data step by step through the RGBA value of PNG graph.

PS: the content studied in this paper has been integrated into an open source JS library calledSphinxJS, interested students can move to this articleSphinxjs — an ultra lightweight open source library that encodes strings into PNG imagesGo and see the relevant documents. Welcome to star!

principle

We know that by setting for static resourcesCache-ControlandExpiresThe response header can force the browser to cache it. When the browser makes a request to the background, it will first look in its own cache. If there is no cache, it will continue to request this static resource from the server. Using this, we can store some information that needs to be cached through this static resource caching mechanism.

So how do we write information to static resources?canvasProvided.getImageData()Methods and.createImageData()Methods can be used separatelyreadandset upPicturesquergbaValue. So we can use these two APIs to read and write information.

Next, look at the schematic diagram:

Exploring front-end black technology -- caching data through RGBA value of PNG graph

When a static resource enters the cache, any subsequent request for the image will first find the local cache, that is, the information has actually been cached locally in the form of an image.

Note that due torgbaThe value can only be an integer between [0, 255], so the method discussed in this paper is only applicable to data composed of pure numbers.

Static server

We usenodeBuild a simple static server:

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')

The function of this static resource is very simple. It provides two functions: generate pictures through Base64 transmitted from the client and save them to the server; Set the picture cache time and send it to the client.

The key part is to set the response header:

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'
})

We’ve set a year’s for this pictureContent-TypeAnd ten yearsExpiresIn theory, it’s long enough. Let’s code the client.

client

<!-- client.html -->

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

Suppose we need to store 32-bit data, so we set the width of canvas to 8 and the height to 1. Why the corresponding length of 32-bit data is 8 is because each pixel has onergba, corresponding toredgreenblueandalphaFour values, so you need to divide by four.

<!-- 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)

First, we assume that the string to be cached is 32 bits01234567890123456789012345678901Then we use.createImageData(8, 1)Generate a blankimgDataObject. Next, we assign a value to the empty object. In order to make the experimental effect more intuitive, wergbaValues are magnified. Setting is finishedimgDataLater, through.putImageData()Method put it into our canvas.

We can print it now and look at thisimgDataWhat is it?

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

Next, we will compile the canvas into Base64 of an image and send it to the server. At the same time, we will receive the response from the server and cache the image:

$.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 ('complete picture request and cache ')
            ctx.drawImage(img, 0, 0)
            console.log(ctx.getImageData(0, 0, 8, 1).data)
        }
    }
})

The code is very simple, through.toDataURL()Method sends Base64 to the server. After processing, the server generates a picture and returns it. Its picture resource address ishttp://xx.xx.xx.xx:80/out.png。 stayimg.onloadAfter that, in fact, the picture has been cached locally. In this event, we print the picture information as a comparison with the source data.

Result analysis

Start the server and run the client. When loading for the first time, you can see the response picture information through the console:

Exploring front-end black technology -- caching data through RGBA value of PNG graph

200 OK, it is proved that it is a picture obtained from the server.

Close the current page and reload:

Exploring front-end black technology -- caching data through RGBA value of PNG graph

200 OK (from cache), proved to be a picture read from the local cache.

Next, look directlyrgbaComparison of values:

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

Cache data: [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]

As you can see, the source data is different from the cached dataBasically consistent, inalphaThe error of the value is too large, inrgbWithin valueOccasional error。 Through analysis, it is considered that the error is caused by the data change caused by the operation involved in the process of Base64 to buffer at the serverTo be verified

According to the previous conclusion, the reason for the error between the source data and the cached data is determined after verificationalphaValue. If we putalphaThe value is directly set to 255 and only the data is stored in thergbValue to eliminate the error. Here are the improved results:

Source data: [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]

Cache data: [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]

Because I’m lazy, I just putalphaThe value is given as 255 without updating the logic of circular assignment, so the metadata of bit 4N is directly replaced with 255, which is leftThe reader can modify it by himselfChange it when you have time

To sum up, this black technology that uses the RGBA value of PNG graph to cache data is theoretically feasible,However, in the actual operation process, more influencing factors may be considered, such as trying to eliminate the error of the server and adopting fault-tolerant mechanism.In fact, it is also feasible.

It is worth noting that,localhostResources may be requested directly through the local instead of the server by default, so in the local experiment, you can set the headercorsCross domain, and simulate server access by setting IP address and port 80.

Postscript

It’s called black technology. In fact, the principle is very simple. Similar to it, there is throughEtagAnd other methods for strong caching. The purpose of research is only for learning and should not be used for illegal purposes. If readers find any mistakes and omissions in this article, they are welcome to correct them. They also hope that interested friends can discuss it together.

Thank you for reading. I’m jrain. Welcome to pay attentionMy column, I will share my learning experience, develop experience and carry dry goods outside the wall from time to time. See you next time!