JSON bag - bInary multipart JSON response
In a scenario in which I am involved we have an embedded server that answers the status of activities and devices combining structured data (JSON) and medium sided binary data (images). This post and the associated github repository (here) presents a AJAX response that combines JSON data with one or more binary payloads.
The objectives are the following:
- avoid base64 bandwidth and decoding overhead
- avoid custom encodings such JSONB/MessagePack that are slow in browser decoding
- reduce number of requests or more complex production logic (if not using HTTP 2.0 Server Push)
- usable for file serialization
- graceful degradation (Content negotiation and JavaScript support)
The approach is the following:
- data encoding, transferred as JavaScript ArrayBuffer, that contains readable header, main JSON and then binary blocks each with a JSON header
- processing on the client side that patches the JSON replacing stub URIs with the new Blob URIs.
- server-side generation that can produce both the JSON bag and a single JSON based on data: URI
A related approach can be found in the binary glTF extension (KHR_binary_glTF). Apart the Binary glTF specific domain features, the differences are:
- JSON-bag exposes everything as URL easing the usage. This is performed by patching the JSON
- the header is human readable
Another solution is the one of multipart/related as also used by CouchDB when the options "Accept: multipart/related" is provided:
- First HTTP Headers
- First Payload
- Then other Payloads with their headers
Structure
The serialization structure is the following:
Server Side
On the server the application generates a JSON with some binary attachment (e.g. files or blocks) each with its own mime. The application knows that this attachment will be written in the output JSON as a URL. Possible URIs employed:
- data: the classic base64 embedding
- http: storing the contents in separate URL
- blob: the new URL type supported by the Blob class and created as a view over the ArrayBuffer
- linkdata: a custom URI that is used to mark the offset, size and mime of an entity wrt the binary payload. This is the one used in the JSON-bag
Depending on the Content-negotiation and configuration the server decide which URI use. In the JSON-bag case the JSON contains a refdata. The Blob header points to the JSON location so that it can be patched on the client side.
For example:
[ { "name" : "image1", "url" : "linkdata:image/png;offset=52;size=20241" }, { "name" : "image2", "url" : "linkdata:image/png;offset=20345;size=20241" } ]
This JSON is associated to the binary block headers: {"mime":"image/png","path":"/0/url","size":20241}
Client Side
On the client side the JSON bag is parsed and each binary block is mapped to a Blob URI. Then the JSON is patched by replacing the linkdata with the final blob url. In this way the html page can use the URL for direct use (e.g. img element). When the URL is not needed the application can use the linkdata URL for manually accessing the ArrayBuffer.
Demo
The server-client demo, implemented in C++ with the embedded server Mongoose shows the case of AJAX request: a JSON-bag is loaded containing a number of images that are shown in the page.
For the impatient I have made an offline version that takes data from the HTML page available here: https://eruffaldi.github.io/jsonbag/indexoff.html. This demo takes an embedded JSON-bag file, simulates an AJAX request and behaves like the previous one.
Format
The JSON bag format is the following usign the ABNF notation:
jsonbag = header JSONcontent separator *blob
header = magic version headersize crlf
headersize = hex sie
magic = "JBAG"
version = "0000"
separator = crlf \x00
blob = blob-header blob-payload
blob-header = headersize JSONcontent
headersize = uint16-le
blob-payload = *uin8
The blob JSON header contains:
- src (string)
- mime (string)
- size (integer)
Comments