Protocol
The protocol is very simple. A bundler is attached to an endpoint /bundle
by default. It receives an array of options
by PUT method as JSON. options
can be objects described below, URLs as strings, or a mix of both.
It verifies a bundle (all URLs should be whitelisted, empty payload, and big payloads are rejected, and so on). If everything is alright, it issues I/O requests (usually in parallel), collects responses and sends back a JSON object of the following format:
{
"bundle": "bundle",
"results": [
// a list of result items
]
}
Every result item has a following structure:
{
"options": {"url": "/abc"}, // options object
"response": {
"status": 200, // an HTTP status code
"statusText": "OK", // an HTTP status text corresponding to status code
"responseType": "", // taken from options.responseType, or an empty string
"responseText": "[1,2,3]", // a payload as a string
"headers": "Content-Type: application/json" // raw headers
}
}
If options
is a string, it is assumed to be a URL for a GET request. Otherwise, following properties are recognized:
-
url
is the only required property. It is a URL of an endpoint we deal with.
The rest of properties are all optional with reasonable defaults:
-
method
is an HTTP method (including PATCH) as a string. Default:'GET'
. -
query
is a query dictionary (a key/value hash), which is used to form a query part of URL after'?'
. Values of such dictionary can be strings, or arrays of strings to form multiple values with the same key. If URL already contains a query part, it is added as is without checking for duplicates. Default: none. -
data
is a data object to send. For GET method it is assumed to be a query object, ifquery
is not specified. For all other requests, it is assumed to be a payload.data
is assumed to be a JSON object. Default: none. -
headers
is a dictionary (a key/value hash), which is used to set request headers. Values of such dictionary can be strings, or arrays of strings to form multiple values with the same key. Default: none, but if there is noAccept
header, it is set toapplication/json
.
The next batch of properties is directly related to an underlying XHR request:
-
user
is a user name as a string to be sent with the request. Default: not sent. -
password
is a password as a string. It is used only ifuser
is specified. Default:''
. -
timeout
is a wait time for a request in milliseconds as a number. Default: not set. -
responseType
is a requested response type as a string. It can be:'json'
,'arraybuffer'
,'blob'
,'document'
,'text'
, or''
. Essentially it defines an automatic conversion of a received response inresponse
property of XHR, which is used byio()
to return a value. Default: not set. -
mime
is a string used to override a returned MIME type inContent-Type
response header. Default: not set.
Starting with version 1.0.5 of heya/io, it understands to recreate responseText
from JSON representation. For that to work following conditions should be met:
-
responseType
is"json"
. -
responseText
is ignored. In order to save some bandwidth it is advisable not to include it at all. -
response
is a valid JSON object to be transferred. -
headers
are to includeContent-Type
asapplication/json
for consistency.
It allows to reformulate the previous example like that:
{
"options": {"url": "/abc"}, // options object
"response": {
"status": 200, // an HTTP status code
"statusText": "OK", // an HTTP status text corresponding to status code
"responseType": "json", // should be "json"
"response": [1,2,3], // a payload as an object
"headers": "Content-Type: application/json" // raw headers
}
}
This extension is used mainly for debugging, because usually a debugger makes it easier to inspect an object, than its textual representation — the former can be represented in a hierarchical fashion, while the latter looks like a huge string with multiple escaped characters.
Another reason is that sometimes we already have JSON objects to send. Using this extension we can avoid an extra call to JSON.stringify()
.
In order to profile an I/O implementation, bundler may include optional time
property, which is a number in milliseconds that takes to process a request.
This property can be added on an item level to measure how much time did it take to do an individual I/O request:
{
"options": {"url": "/abc"}, // options object
"time": 200, // in ms
"response": {
// a response object described above
}
}
It can be added on a bundle level to measure the whole bundle:
{
"bundle": "bundle",
"time": 300, // in ms
"results": [
// a list of result items
]
}
This reference implementation implements fully the main protocol and its optional time extension.