§ Reference · Data and formats
HTTP state servers
The object protocol that the http backend speaks, the conditional requests that the locks need, and how pravo retries and checks a server.
On this page
The http backend keeps the same objects as the S3 backend: one for each record, one for each lock and the transactions, outputs and stored plans. It reaches them through a small HTTP protocol under the url of $settings.state, so that a team can keep a state on a server of its own. Settings lists the keys.
$settings: state: {backend: "http", url: "https://state.example.com/team/app"}
The protocol
A key such as resources/api.config.json is the path below the URL. Every request carries Authorization: Bearer TOKEN, or basic authentication when the settings give a user name.
| Request | Meaning | Answers |
|---|---|---|
GET URL/KEY |
Read an object. | 200 with the body and an ETag header, or 404. |
PUT URL/KEY |
Write an object. | 200, 201 or 204. |
PUT URL/KEY with If-None-Match: * |
Create the object only if none exists. | A success, or 412 when the object exists. |
PUT URL/KEY with If-Match: ETAG |
Replace the object only if it still has that tag. | A success, 412 when the tag differs, or 404 when there is no object. |
DELETE URL/KEY |
Delete an object. | A success, or 404. |
DELETE URL/KEY with If-Match: ETAG |
Delete the object only if it still has that tag. | As for PUT. |
GET URL/?prefix=DIR/ |
List the keys below DIR/. |
200 and {"keys": ["DIR/a.json", "DIR/b.json"]}, with the keys written relative to the URL. |
The listing is not paged: the server sends every key below the prefix. A key that ends in / is not an object and is skipped.
Locks, the replacement of a record that a command read, and the stored plans rest on the three conditional requests, as they do for S3-compatible stores. A server that honours all three keeps two applies apart. A server that ignores one lets two commands hold the same lock, and nothing says so. The tag of an object must change whenever its contents change, and the server must apply a conditional request and the write it guards as one step.
Retries
A request is sent again when the server answers 429, 500, 502, 503 or 504 and when the connection fails. It is sent up to six times, with a random wait that doubles from 100 ms up to 5 s.
A conditional write whose answer was lost may have been applied. When pravo sends it again and gets 412, it reads the object and counts the write as made if the object holds the bytes that were sent. When the server stays busy, or the read fails, the error says the store did not say whether the write was made, and the command stops with exit code 1. Run it again.
Test a server
Before a command that writes the state first uses a server, pravo probes it. It writes scratch objects under probe/, tries each conditional request with a tag that must be refused and with one that must be accepted, and starts eight creates of one key at once, which must have one winner. The scratch objects are deleted at the end. The verdict is remembered for 30 days by URL, and PRAVO_S3_PROBE=off skips the probe.
pravo state probe runs the probe and prints one line for each check. pravo protect check also requires that eight replaces with one tag have one winner, and that a key just written is in the listing.
Try the protocol
tools/fakehttp in pravo's source is an in-memory server for the protocol. It forgets everything when it stops. The examples on this page run against it on port 9000. Start it in a second terminal to follow along:
go run ./tools/fakehttp -addr 127.0.0.1:9000
Point a root at it. A directory that holds only this block is enough to try a server:
package probe
import "pravo.dev/pravo"
$settings: pravo.#Settings & {
state: pravo.#HTTPState & {backend: "http", url: "http://127.0.0.1:9000/demo"}
}
pravo init makes the module, and pravo state probe runs the checks against the server:
Configuration does not load yet; generating schemas for the declared providers first.
Created cue.mod/module.cue
Providers:
pravo has been initialised. Run "pravo plan" to see what it would do.
create only (If-None-Match) ok
replace (If-Match on a put) ok
delete (If-Match on a delete) ok
concurrent creates ok
http://127.0.0.1:9000/demo honours the conditional requests that the locks need.
A command that writes the state creates meta.json and the other objects under the URL. A root with no resources has none to list, and every checkout with the same url reads the same state:
No changes. Your infrastructure matches the configuration.