http://restd.net/service?/=/resource/key

services

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.

resources

A single restd file equates to a single resource.

A restd service provides access to one or more resources.

base service specification

The base restd services accept verbs, manipulate restd files and return data & status codes as defined below.

service URI

There are no restrictions on a restd service URI. Any location addressable over HTTP is valid regardless of programming language.

resource argument

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.

get

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.

everything

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.

just one

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.

query

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.

status

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
  • client requested a full resource without key
  • zero or more objects were successfully returned
200 – ok
  • client requested a specific object by key
  • it was successfully returned
200 – ok
  • client queried for objects by passing a q argument
  • zero or more objects were successfully returned
200 – ok
  • request URI is not parsable as either a full resource, a specific item of a resource or a query
400 – bad request
  • client requested a full resource or an specific item of a resource
  • the resource file does not exist on the server
404 – not found
  • client requested a specific item of a resource
  • the key is greater than the largest index possible in the restd file
404 – not found
  • client requested a specific item of a resource
  • the resource object has been deleted
404 – not found
  • client requested a full resource or a specific item of a resource
  • the resource file exists but the server cannot open it
500 – internal error
  • client requested a full resource or a specific item of a resource
  • any error happens while reading the resource file
500 – internal error
  • client request contains a known feature from a different restd service type
  • (can be ignored and return as if feature was not requested instead)
501 – not implemented

response body

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.

examples

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

[
]

access of restd resource file

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)

post

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.

status

If successful, the restd service will return a 200 status code or an error as determined by the following table.

situation status
  • client posted a new object to a resource
  • the new object was successfully inserted
200 – ok
  • request URI has parameters but none are parsable as a resource
400 – bad request
  • request URI contains a resource and a key
400 – bad request
  • client posted a new object to a resource
  • the resource file does not exist on the server
404 – not found
  • client posted a new object to a resource
  • the JSON for the new object is larger than the blockSize of the restd file
413 – too large
  • client posted a new object to a resource
  • the resource file exists but the server cannot open it
500 – internal error
  • client posted a new object to a resource
  • any error happens while reading or writing to the resource file
500 – internal error

response body

The response body for a successful insert is the restd link for the new item, i.e., a string containing the resource and key.

examples

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"}
]

access of restd resource file

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

put

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.

status

If successful, the restd service will return a 200 status code or an error as determined by the following table.

situation status
  • client updated an existing object in a resource
  • the object was successfully updated
200 – ok
  • request URI does not contain a resource and a key
400 – bad request
  • client updated an existing object in a resource
  • the resource file does not exist on the server
404 – not found
  • client updated an existing object in a resource
  • the key references a location that either does not exist or points to a deleted object
404 – not found
  • client updated an existing object in a resource
  • the JSON for the new object is larger than the blockSize of the restd file
413 – too large
  • client updated an existing object in a resource
  • the resource file exists but the server cannot open it
500 – internal error
  • client updated an existing object in a resource
  • any error happens while reading or writing to the resource file
500 – internal error

response body

Responses to a PUT do not contain a response body.

examples

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

access of restd resource file

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)

delete

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.

status

If successful, the restd service will return a 200 status code or an error as determined by the following table.

situation status
  • client deleted an existing object in a resource
  • the object was successfully deleted
200 – ok
  • client deleted an existing object in a resource
  • the key references a location that either does not exist or points to a deleted object
200 – ok
  • request URI does not contain a resource and a key
400 – bad request
  • client deleted an existing object in a resource
  • the resource file does not exist on the server
404 – not found
  • client deleted an existing object in a resource
  • the resource file exists but the server cannot open it
500 – internal error
  • client deleted an existing object in a resource
  • any error happens while reading or writing to the resource file
500 – internal error

response body

Responses to a DELETE do not contain a body.

examples

Delete a game.

my.svc?/=/games/5

response: 200

access of restd resource file

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)

locking

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.