restd services read & modify restd files and communicate to clients over HTTP.
The primary goal of restd is use of the file format. Any server endpoint that satisfies the one requirement of using restd files is a restd service regardless of the service's public-facing design or intention.
The remainder of this page outlines specific service functionality as both a staring guideline and as a definition of base restd services.
A single restd file equates to a single resource.
A restd service provides access to one or more resources.
The base restd services accept verbs, manipulate restd files and return data & status codes as defined below.
There are no restrictions on a restd service URI. Any location addressable over HTTP is valid regardless of programming language.
Base restd services determine which resource to use with the query parameter /. The value is a / followed by a restd file name minus any extension. For example, a client can access a restd file named games.restd with the argument /=/games.
Issuing GET on a restd service returns an array of the resources currently available.
["games"]
Issuing GET on a restd resource requests objects from that resource.
Calling GET on a resource will return all objects in the resource as determined by the service.
my.svc?/=/games
The above example could return an array of game information owned by the person managing the games restd file.
Adding /key to the end of the resource name requests the resource at the given key in the restd file. In the restd file, the key relates to the objects block offset. The key is zero-based.
my.svc?/=/games/2
This example will return the third game in the restd file.
Adding the q query argument to the GET request performs a case-insenitive, full-text search of the JSON of each stored object and returns an array of all matches.
Separate search tearms with spaces. Strings enclosed in double quotes are considered one search term. An object must contain all terms to be included in the result. This is similar but not identical to the q parameter in Google's Base Data API.
If you required a double quote character inside a quoted search term, use double double quotes.
A client can determine success or failure through the HTTP response status code. This table lists possible outcomes and the status code returned by each.
situation | status |
---|---|
|
200 – ok |
|
200 – ok |
|
200 – ok |
|
400 – bad request |
|
404 – not found |
|
404 – not found |
|
404 – not found |
|
500 – internal error |
|
500 – internal error |
|
501 – not implemented |
Successful responses return a 200 status code and JSON data. The response type is always application/json.
A request for a specific object by key returns that object alone, e.g., the response body begins with an open 'stash: {
A request for a full resource returns a JSON array of objects, e.g., the response body begins with a square bracket: [
restd services should extend the returned objects with a _restd property. The value is a JavaScript string that describes the specific object in the restd service. restd attempts to harness the more static nature of the web in that deleting one object will not change the public uri of any other object in a restd service. The side-effect of this design is that requesting all the data in a restd service will leave deleted objects out of the resulting JSON array. An object's restd key may not match its position in the resulting array.
Some games, pretending that I bought one between these three but sold it.
my.svc?/=/games
response: 200
[ {"_restd":"/games/0","name":"Secret of Monkey Island","genre":"adventure"}, {"_restd":"/games/2","name":"Final Fantasy XIII","genre":"RPG"}, {"_restd":"/games/3","name":"Metroid: Other M","genre":"action"} ]
Just one of the games.
my.svc?/=/games/2
response: 200
{"_restd":"/games/2","name":"Final Fantasy XIII","genre":"RPG"}
Search for games with the word fantasy anywhere in the JSON of the object, e.g., name or genre.
my.svc?/=/games&q=fantasy
response: 200
[ {"_restd":"/games/2","name":"Final Fantasy XIII","genre":"RPG"} ]
Double double quotes is useful if you know your data, JSON surrounds strings with double quotes so this query would only find the original Metroid game and not Metroid: Other M.
my.svc?/=/games&q="""Metroid"""
response: 200
[ ]
The following pseudo-code describes the file access required during a restd request for a whole resource. Error checking is not included.
type header { bomSize, headerSize, blockSize, metaSize } function readHeader(fileName) resource = open(fileName, read-only, share read/write) preamble = read(resource, 3) bomSize = (preamble == 0xef, 0xbb, 0xbf ? 3 : 0) seek(resource, 0, beginning) headerString = read(resource, 64) headerSize = regex.match(headerString, '"headerSize:"(<value>)').capture('value') blockSize = regex.match(headerString, '"blockSize:"(<value>)').capture('value') metaSize = regex.match(headerString, '"metaSize:"(<value>)').capture('value') close(resource) return header(bomSize, headerSize, blockSize, metaSize) function getResource(fileName) header = readHeader(fileName) resource = open(fileName, read-only, share read/write) seek(resource, header.bomSize + header.headerSize, beginning) output.write('[') objectBuffer = read(resource, header.blockSize) while objectBuffer.length == header.blockSize if objectBuffer[0] != ' ' output.write(trim(objectBuffer)) seek(resource, header.metaSize) objectBuffer = read(resource, header.blockSize) output.write(']') close(resource)
If the client is only interested in one object, this pseduo-code will retrieve it.
function getItem(fileName, key) header = readHeader(fileName) resource = open(fileName, read-only, share read/write) seek(resource, header.bomSize + header.headerSize + (header.blockSize + header.metaSize) * key, beginning) output.write(read(resource, header.blockSize)) close(resource)
If the client wishes to query for one or more objects, this pseduo-code will retrieve search for them. The parseQuery function will break the q argument passed via the URL into search terms. We need to temporarily replace two double-quotes with a marker. They will become single double-quote characters in the search term.
function parseQuery(query) searchTerms = [] if query != '' query = query.replace('""', '{twodoublequotes}') matches = regex.matches(query, '"[^"]+?"|[^"\s]+') for i = 0; i < matches.length; i++ searchTerms[i] = matches[i].remove('"').replace('{twodoublequotes}', '"') return searchTerms function query(fileName, query) header = readHeader(fileName) searchTerms = parseQuery(query) resource = open(fileName, read-only, share read/write) seek(resource, header.bomSize + header.headerSize, beginning) output.write('[') objectBuffer = read(resource, header.blockSize) while objectBuffer.length == header.blockSize if objectBuffer[0] != ' ' and hasTerms(objectBuffer, searchTerms) output.write(trim(objectBuffer)) seek(resource, header.metaSize) objectBuffer = read(resource, header.blockSize) output.write(']') close(resource)
The POST verb inserts a new object into a restd resource. The request URI specifies only the restd resource. The request body contains the JSON for the new object. Service developers should ignore _restd properties supplied with objects, they are only intended as a response to GET requests.
my.svc?/=/games
Request body for a new game.
{"name":"Bit.Trip Runner","genre":"platform"}
The request type is application/json.
If successful, the restd service will return a 200 status code or an error as determined by the following table.
situation | status |
---|---|
|
200 – ok |
|
400 – bad request |
|
400 – bad request |
|
404 – not found |
|
413 – too large |
|
500 – internal error |
|
500 – internal error |
The response body for a successful insert is the restd link for the new item, i.e., a string containing the resource and key.
Add a new game.
my.svc?/=/games {"name":"Bit.Trip Runner","genre":"platform"}
response: 200
/games/4
Add one more game for good measure.
my.svc?/=/games {"name":"Sneak King","genre":"stealth","platform":"xbox"}
response: 200
/games/5
It does not not matter that no other games have a platform property. However, a search for just the word platform will return both games in the platform genre as well as any game that has a platform property.
my.svc?/=/games&q=platform
response: 200
[ {"_restd":"/games/4","name":"Bit.Trip Runner","genre":"platform"}, {"_restd":"/games/5","name":"Sneak King","genre":"stealth","platform":"xbox"} ]
The following pseudo-code describes the file access required for posting a new object to a restd resource. Error checking is not included, readHeader is defined above.
While editing a restd file in a service, we lock the service processes to allow only one edit at a time. However, we open the file in shared-read mode so get requests are still allowed. Operating systems can handle this.
When posting a new item to the end of the file, we have to rewrite the restd file footer.
function postItem(fileName, json) header = readHeader(fileName) lock fileInfo = getFileInfo(fileName) entityCount = (fileInfo.length - header.headerSize) / header.blockSize insertOffset = header.bomSize + header.headerSize + entityCount * header.blockSize; resource = open(fileName, read-only, share read) seek(resource, insertOffset, beginning) write(resource, json) write(resource, ',') write(resource, ' ', header.blockSize - json.length - 1) write(resource, 'null]}') close(resource) return entityCount
The PUT verb updates an existing object in a restd resource. The request URI specifies the restd resource and key. The request body contains the JSON for the updated object. Service developers should ignore _restd properties supplied with objects, they are only intended as a response to GET requests.
The base service only permits updates on existing, non-deleted objects. Application developers may update objects with {} to remove the object but keep its key active keeping in mind that these empty objects will be returned on GET requests for the entire resource.
If successful, the restd service will return a 200 status code or an error as determined by the following table.
situation | status |
---|---|
|
200 – ok |
|
400 – bad request |
|
404 – not found |
|
404 – not found |
|
413 – too large |
|
500 – internal error |
|
500 – internal error |
Responses to a PUT do not contain a response body.
Update a game.
my.svc?/=/games/4 {"name":"Bit.Trip Fate","genre":"shooter"}
response: 200
Attempt to update a deleted game.
my.svc?/=/games/1 {"name":"Diablo II","genre":"hack and slash"}
response: 404
The following pseudo-code describes the file access required for updating an existing object in a restd resource. Error checking is not included, readHeader is defined above.
function putItem(fileName, key, json) header = readHeader(fileName) lock insertOffset = header.bomSize + header.headerSize + key * header.blockSize; resource = open(fileName, read-only, share read) seek(resource, insertOffset, beginning) write(resource, json) write(resource, ',') write(resource, ' ', header.blockSize - json.length - 1) close(resource)
The DELETE verb removes an existing object in a restd resource. The request URI specifies the restd resource and key. No request body is required.
A DELETE operation should fill the entire block with space characters. Keys for other objects should not change.
If successful, the restd service will return a 200 status code or an error as determined by the following table.
situation | status |
---|---|
|
200 – ok |
|
200 – ok |
|
400 – bad request |
|
404 – not found |
|
500 – internal error |
|
500 – internal error |
Responses to a DELETE do not contain a body.
Delete a game.
my.svc?/=/games/5
response: 200
The following pseudo-code describes the file access required for deleting an existing object in a restd resource. Error checking is not included, readHeader is defined above.
function putItem(fileName, key, json) header = readHeader(fileName) lock insertOffset = header.bomSize + header.headerSize + key * header.blockSize; resource = open(fileName, read-only, share read) seek(resource, insertOffset, beginning) write(resource, ' ', header.blockSize) close(resource)
Unlike other document-based data stores such as CouchDB, restd performs un-cached locking during updates. This makes it slower than true data servers but restd is designed for publishing smaller or read-only datasets, situations where locking can simplify storage & access and should not be too noticable.