0.2.0 • Published 9 years ago

service-host v0.2.0

Weekly downloads
3
License
MIT
Repository
github
Last release
9 years ago

service-host

Build Status

Provides a configurable JavaScript host which exposes functions to network requests. Intended to provide the low-level bindings for other languages to access a JavaScript environment.

There are a variety of projects offering execution of JavaScript (ExecJS et al), but their performance tends to lag as they typically spawn new environments on every call. By using a persistent JavaScript environment, we gain massive performance improvements and the ability to persist state.

Behind the scenes, Node is used to provide a platform with an enourmous ecosystem, robust support for asynchronous programming, and solid debugging capabilities.

Installation

npm install service-host

Basic usage

Create a file services.config.js with configuration for the host. For example:

module.exports = {
  port: 9009,
  services: {
    some_service: function(data, cb) {
      cb(null, 'Hello, World!');
    }
  }
});

Start the host with:

node_modules/.bin/service-host services.config.js

And call the some_service service by sending a POST request to http://127.0.0.1:9009/service/some_service.

Services

Services are functions which the hosts exposes to incoming requests. Services are provided with two arguments:

  • data is an object generated from deserializing the data sent in the request.
  • cb is a function which should be called once the service has completed or encountered an error. cb assumes that the first argument indicates an error, and the second argument indicates success.

Handling success

Once your service has completed successfully, you should pass a value to cb as the second argument. For example:

cb(null, {status: 'success'});

Note: the value of the second argument is sent back to the caller as a text response. If the value is an object, it will be serialized to JSON. Types other than objects will be coerced to strings.

Handling errors

You should try to gracefully handle any errors encountered. Any uncaught errors will cause the host to assume the worst and exit immediately. If you encounter an error condition, pass an Error instance to cb, and let the host handle it. If you need to execute code that may throw errors, use a try/catch, pass the error to the host, and exit your function. For example:

function(data, cb) {
  try {
    dangerousFunc();
  } catch(err) {
    return cb(err);
  }
  cb(null, 'ok');
};

Note: if you use a try/catch statement, remember to exit the function with return when sending errors back.

If you encounter a condition deserving of an error, always provide Error objects rather than strings. For example:

// Bad
cb('Something bad happened');

// Good
cb(new Error('Something bad happened'));

By using Error objects, the host is able to provide an accurate stack trace, indicating the source of the error.

Accessing the host from a service

Services can access their host via this.host. For example:

// Write to the host's logs
this.host.logger.info('Something happened');
this.host.logger.warn('Something bad might happen');
this.host.logger.error('Something bad happened');

Note: the this binding will not be passed along to other functions. You need to either pass values explicity or pass the this binding along. For example:

function(data, cb) {
  this.host.logger.info('Starting service');
  
  // Pass values explicitly
  logData(this.host.logger, data);
  
  // Create a new function which uses the outer `this` binding
  someAsyncFunc(function(err, res) {
    if (err) {
      this.host.logger.error('Something bad happened');
      return cb(err);
    }
    cb(null, res);
  }.bind(this));
}

function logData(logger, data) {
  logger.info('So much data...', data);
};

Note: the this binding of a service is generated per-request. Values added to the this object will not be passed along to other requests.

Config files

Config files are simply JS files which export an object, for example:

module.exports = {
  port: 8080,
  services: {
    some_service: function(data, cb) {
      // ...
    }
  }
};

Config objects may possess the following attributes:

address: the address that the host will listen at. Defaults to '127.0.0.1'.

port: the port number that the host will listen at. Defaults to 9009.

requestDataLimit: The maximum size allowed for a request's body. Defaults to '10mb'.

cacheTimeout: The time period in milliseconds before the cache will expire an entry. Defaults to 24 hours.

logger: An object which will be used instead of the default logger. The object must provide a similar API to the console object, eg: it must provide functions named log, error, info, etc.

services: a key/value object with service names as keys and functions as values. Values may also be objects which expose a function under a property named handler.

Calling the services

Services are exposed to POST requests at the /service/<name> endpoint.

To send data: set the request's content-type to application/json and pass JSON as the request's body.

Service output can be optionally cached by adding a key query param to your requests, for example: /service/some_service?key=<key>.

If a key is provided and the service provides a success response, all subsequent requests will resolve to the same output until the cache expires it.

Note: if concurrent requests for a service use the same key param, the first request will trigger the call to the service, and the other requests will be blocked until the first has completed. Once the service completes, all concurrent requests are provided with either the error or success output.