Serving
A server holds a series and answers small requests about it: its
description, tiles for a chart in a browser, and images. The protocol is
plain HTTP GET, so any client, cache and proxy understands it.
nonius-server is its reference server, a program you install with
Cargo:
cargo install nonius-server
This chapter serves the readings of Your data: one point a millisecond for 100 seconds, in a CSV.
From a CSV to a running server
nonius-server csv readings.csv readings.nsb
nonius-server build readings.nsb readings.pyr.nsb --base-ns 1000000 --levels 4
nonius-server verify readings.pyr.nsb
The first line writes the series file. The second builds a pyramid file: the series and its pyramid, stored as the protocol’s tiles are, so a tile is read straight from it. The third checks its hashes and builds the pyramid again to compare. Each says what it did:
readings.nsb: 100000 points from readings.csv; 2 decimal places, the most any value has; in time order
readings.pyr.nsb: 100000 points, 3 levels kept from level 1, 32810 buckets; 4.5 MB, 45.0 B a point
The points are a millisecond apart, so the base is a millisecond. Level 0 would hold one point a bucket, and was released (see What it costs): three levels are kept.
A server can serve the plain series file too, building the pyramid in memory as it starts. A pyramid file is built once and then mapped: the server starts at once, whatever its size, and only the parts a request reads are brought into memory.
nonius-server readings.pyr.nsb --id readings --addr 127.0.0.1:8080 &
The & leaves it running in the background, for the commands below. A
series’ id is its file’s name unless --id names it; several files
serve several series.
Asking it
The series’ description, its metadata:
curl -s http://127.0.0.1:8080/v1/series/readings
{"protocol":1,"series":"readings","base_ns":"1000000","levels":4,"ratio":4,"tile_columns":2048,"raw_tile_ns":"4096000000","max_k":4096,"v_exponent":2,"rows":"100000","first_t":"1760000000000000000","last_t":"1760000099999000004","last":{"t":"1760000099999000004","v":"10099","w":"1"}}
The 64-bit integers are strings, because JSON numbers lose precision
past 2⁵³. A browser’s attachTiles reads this first, then asks for
tiles: /v1/series/{id}/columns/{level}/{k}/{i} for summaries and
/v1/series/{id}/raw/{i} for points. Their addresses and contents are
the protocol’s
sections 4 and 5; a chart computes them itself, and you never write
one by hand.
An image of the whole 100 seconds, light, with a title and a unit:
image='http://127.0.0.1:8080/v1/series/readings/png?from=1760000000000000000&to=1760000100000000000&width=1600&height=600&ratio=2&theme=light&title=Readings&unit=%C2%B0C'
curl -s -o readings.png "$image"
from and to are the time shown, [from, to): to is not
included, so the whole series ends at its last point’s time plus one,
or later. width and height are the PNG’s own pixels; ratio=2 draws labels
and lines for a screen of two device pixels a CSS pixel, so this image
fills a box of 800 × 300 on such a screen. The unit °C is
percent-encoded, as a browser’s URLSearchParams writes it. The theme
is dark unless theme=light. Every parameter is on nonius-server’s
page; one the server does not
know is ignored, so a misspelling (timezone= for tz=) quietly falls
back to the default.
Caching
A tile or an image never changes: the same address always answers the
same bytes. The server says so — Cache-Control: public, max-age=31536000, immutable — and gives each answer an ETag, a hash
of its bytes. A client that already has it asks with the tag and gets
304 Not Modified, no body:
etag=$(curl -s -o /dev/null -w '%header{etag}' "$image")
curl -s -o /dev/null -w '%{http_code}\n' -H "If-None-Match: $etag" "$image"
That prints 304. So a browser, a proxy or a CDN can keep tiles and
images for as long as it likes. The metadata is the exception: it says
no-cache, because a series that grows changes its last point.
A page on another origin
A page served from one address reads a server at another only if the
server allows it. nonius-server allows any page: every answer carries
Access-Control-Allow-Origin: *, exposes its ETag and its image
headers to scripts (Access-Control-Expose-Headers), and
Cross-Origin-Resource-Policy: cross-origin, so a page that isolates
itself for the live feed’s shared memory can still read it.
An image also says where its plot is: Nonius-Plot: x y width height,
the plot’s rectangle in the image’s pixels, and Nonius-Range: lo hi,
the values on its bottom and top rows. A page showing the PNG turns a
click into a time and a value with them, and asks the server’s
/pick for the point under it.
In production
nonius-server is the protocol’s reference and a test fixture, not a
production server: no TLS, no limit on connections, one thread a
connection. In front of users, put it behind a reverse proxy that
terminates TLS and limits connections; since every answer but the
metadata is immutable, the proxy can cache them all.
For a server of your own, two ways:
- The protocol inside your Rust program, from the crate’s library:
#![allow(unused)]
fn main() {
let served = Served::new("day", (t, v, w), 2, Options::default())?;
}
served.answer(&request) answers a request with no socket, and
serve puts it on a TcpListener; the
crate’s page shows both.
- The protocol in any language. It is written to be implemented without Nonius’s code: the protocol gives every address, byte and rule, and the images are drawn by the .NET package as by Rust.