<%- include nav.html %>

ShellyJS

Tutorials

API Functionality

APIs are defined in modules with specific members and functions.

Functions may be accessed via HTTP, WebSocket, or TCP.

On each call paramters are verified and security access is checked.

The following is a very simple example of an API module:

var sh = require(global.C.BASE_DIR + "/lib/shutil.js");

var example = exports;

example.desc = "example api";
example.functions = {
  echo: {desc: "one parameter api example", params: {param1: {dtype: "string"}}, security: []}
};

example.echo = function (req, res, cb) {
  res.add(sh.event("example.echo", req.body.param1));
  return cb(0);
};

API Structure

There are two classes of APIs - Core and App. The API definitions and capabilities are identical. The purpose of the segmentation is mostly to facilitate code separation for upgrades.

Core APIs

These contain basic functionality such as registration, counters, basic game play, and others. They ship with Shelly and are located in:

~/node_modules/shellyjs/apis/

They can be viewed in the admin via the "Core APIs" menu: http://localhost:5100/core.html

App APIs

These are the developer defined APIs and are located in the APP_API_DIR config setting

shelly.start({
  APP_API_DIR: __dirname + "/apis",
});

They can be viewed in the admin via the "App APIs" menu: http://localhost:5100/core.html?api=api.app

NOTE: APP_API_DIR defaults to the ~/node_modules/shellyjs/example/apis directory.

App Definitions

Shelly looks for any files in the APP_API_DIR with the flowing format:
~/apis/<API name>/<API name>.js

App Setup

Follow the quick start to make sure Shelly is installed property

Quick Start

NOTE: By default Shelly will use a developer data store (sqlite). This is intended for ease of setup and should be changed for any production environment.

Create your own Shelly App:

$ cp ~/node_modules/shellyjs/example/* .

This will create the flowing

                ./app.js - mail application file
                ./apis - directory where to place your APIs
                ./apis/example/example.js - example api to be used as a template
                ./games - directoy used by the Games Core API

Run your own app:

$ node app.js

Test your setup:

  1. Lauch the admin test page: http://localhost:5100/core.html?api=api.app
  2. Click the "example" API in the accordian to the left, then click the "hello" function below it.
  3. To invoke the function click the "call function" button.
  4. Try the "echo" function and alert the calling JSON by filling in the "param1" data member.

Add a function description:

To allow for parameter, security and easy testing each API function has a description member in the <module>.functions object

Edit the file ~/apis/example/example.js

var sh = require(global.C.BASE_DIR + "/lib/shutil.js");

var example = exports;

example.desc = "example api";
example.functions = {
  echo: {desc: "one parameter api example", params: {param1: {dtype: "string"}}, security: []},
  hello: {desc: "my first api function", params: {param1: {dtype: "string"}}, security: []}
};

example.echo = function (req, res, cb) {
  res.add(sh.event("example.echo", req.body.param1));
  return cb(0);
};

In the above code sample line 8 has been added to define the "hello" function in the "example" api

Add a function definition:

Now just create the function to be called with the fixed signature of function(req, res, cb)

Edit the file ~/apis/example/example.js

var sh = require(global.C.BASE_DIR + "/lib/shutil.js");

var example = exports;

example.desc = "example api";
example.functions = {
  echo: {desc: "one parameter api example", params: {param1: {dtype: "string"}}, security: []},
  hello: {desc: "my first api function", params: {myName: {dtype: "string"}}, security: []}
};

example.echo = function (req, res, cb) {
  res.add(sh.event("example.echo", req.body.param1));
  return cb(0);
};

example.hello = function (req, res, cb) {
  res.add(sh.event("example.hello", "hello " + req.body.myName + "!"));
  return cb(0);
};

In the above code sample lines 16-19 define the new function "hello" in the "example" API

This function is now callable from the HTTP, WebSocket, and TCP interface.

The req and res objects are similar to the express req and res objects, but work for WebSockets and TCP also.

You can add multiple response objects. Moreover you can call res.add() multiple times in a single call. The HTTP interface returns an array objects, while the WebSocket and TCP interface return each individual object.

The sh.event function on line 12 and line 17 is just a convenience function that creates a standard envelope around each response. It creates an object like:

{
  "event": "example.echo",
  "ts": 1378189438607,
  "data": "foo"
}

Cluster configuration

Per Server Workers

Each server in a cluster can run multiple workers for the HTTP, WebSocket, TCP, Admin, Game, Mailer, and Matcher processes. These are configured per server in with the CLUSTER_NUM_*settings

global.CDEF("CLUSTER_NUM_SOCKET", 2);
global.CDEF("CLUSTER_NUM_TCP", 1);
global.CDEF("CLUSTER_NUM_REST", 1);
global.CDEF("CLUSTER_NUM_ADMIN", 1);
global.CDEF("CLUSTER_NUM_GAMES", 1);
global.CDEF("CLUSTER_NUM_MATCHER", 1);
global.CDEF("CLUSTER_NUM_MAILER", 1);

In the above example the server will spawn 2 WebSocket workers and one each for the HTTP, TCP, Admin, Games, Matcher, and Mailer worker types.

Multi-server - developer

Servers in a cluster register themselves on startup and can be added and removed dynamically.

To demonstrate clustering in the development environment (sqlite) you can launch a second server on the same machine by creating a different config file so the listening ports do not conflict.

Both servers must have access to he same sqlite database file.

The following example can be found in the ~/node_modules/shelly/examples/config2 directory:

global.CDEF("ADMIN_PORT", 6100);
global.CDEF("REST_PORT", 6101);
global.CDEF("GAMES_PORT", 6102);
global.CDEF("SOCKET_PORT", 6110);
global.CDEF("TCP_PORT", 6111);

global.CDEF("CLUSTER_URL", "tcp://localhost:6151");
// don't run a mail queue processor on this server
global.CDEF("CLUSTER_NUM_MAILERS", 0);

// pick up all the other default configs
require(global.C.BASE_DIR + "/config/main.js");

Start the first server

This assumes you have already copied the examples directory as done in the Create API section

$ node app.js

Start the second server

The default first argument is a valid config directory with a main.js in it.

$ node app.js CONFIG_DIR=./config2

Check the servers

  1. Login to the first server: http://localhost:5102/
  2. Login to the second server: http://localhost:6102/
  3. Go to the admin http://localhost:5100
  4. In the admin go to the "Core APIs" and click on the "cluster" API and the "servers" function
  5. Click the "call function button" and you should see two servers listed.
[
  {
    "event": "cluster.servers",
    "ts": 1378193108072,
    "data": {
      "5c3817f0-d7a2-11e2-964f-d147e3ee063a": {
        "clusterUrl": "tcp://localhost:6151",
        "socketUrl": "ws://localhost:6110",
        "oid": "5c3817f0-d7a2-11e2-964f-d147e3ee063a",
        "created": 1378193106281,
        "modified": 1378193106282
      },
      "a81f9560-dac9-11e2-8aa4-7b114762795c": {
        "clusterUrl": "tcp://localhost:5151",
        "socketUrl": "ws://localhost:5110",
        "oid": "a81f9560-dac9-11e2-8aa4-7b114762795c",
        "created": 1378109876920,
        "modified": 1378109876921
      }
    }
  }
]

Multi-server - two machines

To run a cluster on separate machines the Redis data store wrapper must be used by setting the DB_WRAPPER and DB_OPTIONS in the "example/config/main.js". For more details see Use Redis

global.CDEF("DB_WRAPPER", global.C.BASE_DIR + "/lib/db/shredis.js");
global.CDEF("DB_OPTIONS", {port: 6379, host: "127.0.0.1"});

Then just install and start a server on each machine.

$ node app.js

Install Redis

Use the setup for your platform at Redis.io Redis Install

Change Configuration

Add the following settings to the application configuration file in example/config/main.js

global.CDEF("DB_WRAPPER", global.C.BASE_DIR + "/lib/db/shredis.js");
global.CDEF("DB_OPTIONS", {port: 6379, host: "127.0.0.1"});

By default Redis installs with no password required. Use the DB_OPTIONS to pass in any Redis specific settings. Shelly uses the Redis client. Details for this client can be found at the Redis client GitHub repo.

NOTE: If host and port are set in the DB_OPTIONS configuration, the values are passed as the first and second parameters to redis.createClient(host, port. options). Also redis.auth is called if a password member is set.

If your Redis server has a password required your configuration will look something like:

global.CDEF("DB_WRAPPER", global.C.BASE_DIR + "/lib/db/shredis.js");
global.CDEF("DB_OPTIONS", {port: 6379, host: "127.0.0.1", password:"foo"});

Check the access

  1. Go to the admin http://localhost:5100
  2. In the admin go to the "System" page
  3. Verify the DB_WRAPPER and DB_OPTIONS are set correctly
<%- include footer.html %>