Bytengine documentation

Bytengine is an HTTP based content repository API. It enables you to store JSON and Binary Data in a pseudo hierarchical file system of files and folders.

Typical usage:

  • Content Management System backend
  • Document Management System backend
  • Digital assets storage and delivery server
  • Questionnaire application repository

Quick Tutorial - Python

This guide will give you a quick overview of a few Bytengine api calls.

This guide assumes that you be running Bytengine locally on its default ports. The python code uses the excellent requests module.

Create admin user and start Bytengine server

cd $YOUR_BYTENGINE_SERVER_EXECUTABLE_DIR
./bytengine createadmin -u=admin -p=password
./bytengine run

Connect to Bytengine

>>> import requests
>>> url = "http://localhost:8500/"
>>> r = requests.get(url)
>>> print r.text
{"bytengine":"Welcome","version":"0.2.0"}

Login and create database

>>> url = "http://localhost:8500/bfs/token"
>>> data = {"username":"admin","password":"password"}
>>> r = requests.post(url, data=data)
>>> j = r.json()
>>> print j["status"]
ok
>>> token = j["data"]
>>> cmd = 'server.newdb "test"; server.listdb;'  # issue two commands
>>> url = "http://localhost:8500/bfs/query"
>>> data = {"token":token,"query":cmd}
>>> r = requests.post(url, data=data)
>>> j = r.json()
>>> print j["status"]
ok
>>> print j["data"][-1]  # get last result
[u'test']

Content creation: BQL script: ch1_script.bql

/* ============================================
   This is a BQL comment:

   Multiple commands can be issued in a single
   script but must be separated by a ';'
   Results from the all commands will be
   returned in an array.
=============================================== */

/*---- create directories ----*/

@test.newdir /myapp ;
@test.newdir /myapp/users ;

/*---- create file with valid JSON data ----*/

@test.newfile /myapp/users/u1 {"name":"justin","age":24} ;
@test.newfile /myapp/users/u2 {"name":"lola","age":57} ;
@test.newfile /myapp/users/u3 {"name":"jenny","age":33} ;
@test.newfile /myapp/users/u4 {"name":"sam","age":16} ;

/*---- search for users ----*/

@test.select "name" "age" in /myapp/users
where "age" < 20 or regex("name","i") == "^j";

Load and execute script

>>> with open("ch1_script.bql","r") as f:
...     cmd = f.read()
...
>>> url = "http://localhost:8500/bfs/query"
>>> data = {"token":token,"query":cmd}
>>> r = requests.post(url, data=data)
>>> j = r.json()
>>> j["status"]
u'ok'
>>> len(j["data"][-1])
3

Quick Tutorial - bshell

We are going to use bshell. this time round to execute the commands issued in the previous python tutorial. bshell (Bytengine Shell) makes it much easier to test and build up scripts that can later be used in an application.

We’ll first have to install bshell from source (or download the binaries).

Build from source (requires Go)

This assumes you have setup $GOPATH and have added $GOPATH/bin to $PATH. We’ll also assume that you have Bytengine installed.

go get github.com/johnwilson/bytengine/cmd/bshell
bshell run -u=admin -p=password

Create database

bql> server.newdb "test"; server.listdb;
{
    "data": [
        true,
        [
            "test"
        ]
    ],
    "status": "ok"
}

Content creation: BQL script

We are going to execute the following script using bshell’s editor command

/* ============================================
   This is a BQL comment:

   Multiple commands can be issued in a single
   script but must be separated by a ';'
   Results from the all commands will be
   returned in an array.
=============================================== */

/*---- create directories ----*/

@test.newdir /myapp ;
@test.newdir /myapp/users ;

/*---- create file with valid JSON data ----*/

@test.newfile /myapp/users/u1 {"name":"justin","age":24} ;
@test.newfile /myapp/users/u2 {"name":"lola","age":57} ;
@test.newfile /myapp/users/u3 {"name":"jenny","age":33} ;
@test.newfile /myapp/users/u4 {"name":"sam","age":16} ;

/*---- search for users ----*/

@test.select "name" "age" in /myapp/users
where "age" < 20 or regex("name","i") == "^j";

Open bshell editor (‘vim’ by default) and type/save the above script

bql> \e

You should see the following response

{
    "data": [
        true,
        true,
        true,
        true,
        true,
        true,
        [
            {
                "content": {
                    "age": 24,
                    "name": "justin"
                },
                "path": "/myapp/users/u1"
            },
            {
                "content": {
                    "age": 33,
                    "name": "jenny"
                },
                "path": "/myapp/users/u3"
            },
            {
                "content": {
                    "age": 16,
                    "name": "sam"
                },
                "path": "/myapp/users/u4"
            }
        ]
    ],
    "status": "ok"
}

As we can see, the result for each of the commands issued is returned by the server.

The bshell example is less verbose than the python one which makes it an essential tool when working with Bytengine.

Why Bytengine

Bytengine is designed to be a lightweight content repository for storing JSON documents as well as digital assets. Your data is stored in files that can be organised using directories, similar to a regular OS file system.

A BFS (Bytengine File System) file is made up of two layers, the JSON data layer and the bytes data layer. This allows you for example to store digital assets along with their metadata that can be queried using Bytengine Query Language (BQL).

Bytengine’s role in your application stack is to store form data in JSON format or as raw bytes (if it happens to be a scanned document, word document or pdf document) and to allow you to organise and query that data easily. This means that it is the ideal repository for questionnaire data, cv and application form data, and general business form data. The ability to structure your data into directories also means you can build a sophisticated workflow layer which goes beyond just adding a status field to track a forms progress within a process.

Bytengine is written in Go and is open source so you can extended it to include features particular to your application’s needs.

HTTP API

Bytengine HTTP API allows you to interact with the server using your favorite HTTP client library or application.

Quick reference

URL HTTP Verb Functionality
/ GET Welcome message
/bfs/token POST Get an authentication token
/bfs/query POST Send a BQL request
/bfs/uploadticket POST Get a file upload ticket
/bfs/writebytes/:ticket POST Upload file
/bfs/readbytes POST Download file
/bfs/direct/:layer/:database/*path GET Content static serve

Welcome message

Returns a welcome message from Bytengine. This url is usefull for checking if you’re connected to the server when writing clients in your prefered language.

Example:

curl -X GET http://localhost:8500/

Return Value:

{
    "bytengine": "Welcome",
    "version": "0.2.0"
}

Get an authentication token

Gets an authentication token from the server that must be included in POST requests to interact with content.

Example:

curl -X POST \
    -d 'username=admin&password=password' \
    http://localhost:8500/bfs/token

Return Value:

{
    "data": [token as a string],
    "status": "ok"
}

Server Commands

Server commands can only be run by a user who has root access.

server.init

server.init removes all file system content and returns the names of databases deleted from the server.

Return Value:

{
    "status": "ok",
    "data": [
        db_1,
        db_2,
        ...,
        db_n
    ]
}

Example:

server.init

server.newdb

server.newdb creates a new database.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

server.newdb "db1"

server.listdb

server.listdb list all databases on the server.

Options:

--regex: regular expression string

Return Value:

{
    "status": "ok",
    "data": [
        db_1,
        db_2,
        ...,
        db_n
    ]
}

Example:

server.listdb
server.listdb --regex="^j"

server.dropdb

server.dropdb deletes a database.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

server.dropdb "db1"

User Commands

User commands can only be run by a user who has root access except user.whoami.

user.new

user.new creates a new (non-root) server user.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

user.new "username" "password"

user.all

user.all lists all server users (usernames).

Options:

--regex: regular expression string

Return Value:

{
    "status": "ok",
    "data": [
        "user_1",
        ...,
        "user_n"
    ]
}

Example:

user.all
user.all --regex="^ad"

user.about

user.about returns information about a user (databases, status, etc...).

Return Value:

{
    "status": "ok",
    "data": {
        "active": true,
        "databases": [],
        "root": false,
        "username": "user1"
    }
}

Example:

user.about "username"

user.delete

user.delete deletes a user from the server.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

user.delete "username"

user.passw

user.passw changes user’s password.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

user.passw "username" "newpassword"

user.access

user.access grants/denies user access to the server.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

user.access "username" grant
user.access "username" deny

user.db

user.db grants/denies user access to a database on the server.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

user.db "username" "database" grant
user.db "username" "database" deny

user.whoami

user.whoami show current user’s information.

Return Value:

{
    "status": "ok",
    "data": {
        "databases": [],
        "root": false,
        "username": "user1"
    }
}

Example:

user.whoami

File System Commands

File system commands enable the creation and querying of content. All commands commands must be preceeded by the name of the database with a ‘@’ prefix. File and Directory names cannot include spaces.

database.newdir

database.newdir creates a directory at the given path. Paths are unix/linux style paths (i.e. with forward slash separator) and the last element in the path will be the name of the directory. The root directory (‘/’) is created by default and cannot be modified.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

@db1.newdir /var
@db1.newdir /var/log

database.newfile

database.newfile creates a file and writes the data to the JSON layer. Similarily to directories the file will be created at the given path. Paths are unix/linux style paths (i.e. with forward slash separator) and the last element in the path will be the name of the file. The data must be a valid JSON object.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

@db1.newfile /var/log/file1.log {"events": []}
@db1.newfile /var/log/file2.log {}

database.readfile

database.readfile returns the JSON layer of a file. An array of fields can be added to limit the returned data.

Return Value:

{
    "status": "ok",
    "data": {...}
}

Example:

@db1.readfile /var/logs/file1.log
@db1.readfile /var/logs/file1.log ["field1", "field1.field1_1"]

database.updatefile

database.updatefile overwrites the JSON layer of a file.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

@db1.updatefile /var/logs/file1.log {"field1":{"field1_1": "value"}}

database.deletebytes

database.deletebytes deletes the Bytes layer content of a file.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

@db1.deletebytes /var/logs/file1.log

database.listdir

database.listdir lists the contents of a directory. Content is seperated into directories (dirs), files (files) and files with non-empty ‘bytes layer’ (bfiles). The ‘regex’ option filters the return values.

Options:

--regex: regular expression string

Return Value:

{
    "status": "ok",
    "data": [
        "dirs": [],
        "files": [],
        "bfiles": []
    ]
}

Example:

@db1.listdir / --regex="^v"
@db1.listdir /var/log/

database.rename

database.rename renames a file or directory. The root directory ‘/’ cannot be renamed.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

@db1.rename /var/log "logs"
@db1.rename /var/logs/file1.log "filelog.1"

database.move

database.move moves a file or directory to a new directory.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

@db1.move /var/logs/filelog.1 /var

database.copy

database.copy makes a copy of a file or directory. The last element in the new path will be the name of the copied file/directory.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

@db1.copy /var/logs /var/file_logs

database.delete

database.delete deletes a file or directory. In the case of a directory it performs a recursive delete.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

@db1.delete /var/file_logs

database.info

database.info returns metadata for files/directories such as creation date, parent directory, type etc...

Return Value:

{
    "status": "ok",
    "data": {
        "created": "2014:10:23-17:42:46.5623",
        "type": "file",
        ...
    }
}

Example:

@db1.info /
@db1.info /var/logs/filelog.1

database.makepublic

database.makepublic makes a file publicly available which means it can be accessed directly without authentication. View Direct Content Access for further details. All files are private by default.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

@db1.makepublic /var/logs/file1.log

database.makeprivate

database.makeprivate makes a file private.

Return Value:

{
    "status": "ok",
    "data": true
}

Example:

@db1.makeprivate /var/logs/file1.log

database.counter

database.counter creates a ‘counter’ or global integer value for the database which can be incremented ‘incr’, decremented ‘decr’ or reset ‘reset’. This command can be used to create primary keys for content. ‘database.counter list’ returns all counters and their values in the database.

Return Value:

// database.counter [name] incr [value]
{
    "status": "ok",
    "data": [incremented integer value]
}

// database.counter [name] decr [value]
{
    "status": "ok",
    "data": [decremented integer value]
}

// database.counter [name] reset [value]
{
    "status": "ok",
    "data": [incremented integer value]
}

// database.counter list
{
    "status": "ok",
    "data": [
        {"name":"...", "value": [current integer value]}
    ]
}

Example:

@db1.counter "logs" incr 1
@db1.counter "logs" incr 5
@db1.counter "logs" decr 2
@db1.counter "logs" reset 0

@db1.counter list
@db1.counter list --regex="^l"

Advanced File System Commands

database.select

database.select retrieves fields from files in directories based on field values or file metadata values specified in the Where statement. Additional statements such as Limit, Sort, Count or Distinct can be used to further filter the query result. Multiple field comparison can be seperated by Or (And is implied).

Field value comparison operators are:

  • equal: ==
  • not equal: !=
  • greater than: >
  • greater than or equal: >=
  • less than: <
  • less than or equal: <=

Field inclusion/exclusion operators are:

  • inclusive of: in
  • exclusive of: nin

Field functions available are:

  • type of field: typeof([FIELD]) (supported types are ‘string’, ‘int’)
  • existance of field: exists([FIELD])
  • field regular expression: regex([FIELD], REGEX_OPTION). Valid REGEX_OPTION values can be found in the mongodb documentation.

Special Field values are:

  • file name: file_name
  • file privacy: file_ispublic

Return Value:

{
    "status": "ok",
    "data": [
        {
            "path": "/.../...",
            "content": {...}
        },
        ...
    ]
}

Example:

@db1.select "age" in /users /staff limit 20

@db1.select "name" "course.room" in /students where "year">=1 "status"=="active"

@db1.select "date" in /logs where "event"=="failure" limit 50

@db1.select "" in /users where regex(file_name, "i") == "^j" distinct "age"

@db1.select "" in /members count
@db1.select "" in /members distinct "country"

@db1.select "name" in /banned_users sort asc "date_joined"
@db1.select "name" in /banned_users sort desc "date_joined"

@db1.select "status" in /users where typeof("children")=="int"

database.set

database.set sets file field values in a directory based on Where statement as specified in the database.select command.

Return Value:

{
    "status": "ok",
    "data": [number of affected files]
}

Example:

@db1.set "country"="Ghana" in /users where "country"=="gh"

database.unset

database.unset unsets (or removes) file field values in a directory based on Where statement as specified in the database.select command.

Return Value:

{
    "status": "ok",
    "data": [number of affected files]
}

Example:

@db1.unset "country" in /users where "country"=="gh"

Data Filter Functions

Bytengine allows you to filter/modify commands results by redirecting their output to Filter Functions (or user defined functions). This feature comes in handy especially when you want to process the data server-side before returning it to your application. The syntax to redirect output is >> function_name and can be used after any bfs command.

Currently only a sample function pretty which pretty prints bfs commands is included as an example but further useful ones will be added eventually. You can view the filter function plugin code implementation in /bytengine/fltcore/fltcore.go.

Example:

@db1.select "name" in /users >> pretty

@db1.select "name" in /users >> my_custom_function

Direct Content Access

Bytengine allows you to server your content directly (as static content) via Http GET. You have to first make the bytengine file public by issuing a database.makepublic command which will make the content available at the following url:

http://[your bytengine server address]/bfs/direct/[layer]/[database]/[path]

Where:

  • [layer] is the bytengine file layer you want to retrieve (JSON or Bytes)
  • [database] is the database name
  • [path] is the bytengine file path

The response header will either be application/json if the layer is json and application/octet-stream if the layer is bytes

Example:

http://localhost:8500/bfs/direct/json/db1/users/active/file1

http://localhost:8500/bfs/direct/bytes/db1/users/active/file1_profile_picture

Configuring Bytengine

Bytengine configuration file is in JSON format and can be found at /bytengine/bytengine/config.json.

The authentication (authentication) and bytengine file system (filesystem) plugins use Mongodb. as their database and the mgo driver.

{
    "plugin": "mongodb",                // authentication plugin name
    "addresses":["localhost:27017"],    // mongodb server(s)
    "authdb":"",                        // mongodb authentication database
    "username":"",                      // mongodb authentication username
    "password":"",                      // mongodb authentication password
    "timeout":60                        // mongodb client timeout
}

The state store plugin (statestore), for tokens and cache, relies on Redis.

{
    "plugin": "redis",              // state store plugin name
    "address": "localhost:6379",    // redis server address
    "password": "",                 // redis server password
    "timeout": 60,                  // redis client timeout
    "database": 1                   // database index
}

The byte store (bytestore) plugin uses Diskv. for storage.

{
    "rootdir":"/tmp/bytengine_bst", // directory to store content
    "cachesize":1                   // cache size in mb
}

Other configurations

{
    ...
    "workers": 2,           // number of goroutines
    "port": 8500,           // bytengine server port
    "address": "localhost", // bytengine server address
    "timeout": {
        "authtoken": 60,    // authentication cache timeout in minutes
        "uploadticket": 60  // upload ticket cache timeout in minutes
    }
}

Customising Bytengine