0.6.2 • Published 7 years ago

ipcio v0.6.2

Weekly downloads
1
License
MIT
Repository
github
Last release
7 years ago

ipcIO

Server and client for unix-domain-based inter-process-communication (IPC).

Sounds scary. What does it do to my project?

Provides layer of connectivity, conducted in the same favor as unix processes use to communicate. This is called the Inter Process Communication (IPC).

Basic nodejs net library, literally net.Server and net.Socket, can, aside "regular" TCP sockets, provide the same functionality. To be honest, ipcIO uses them underneath.

So...

What makes this package worth attention, is the way of dealing with handlers, (special functions passed to ipcIO server or client classes) and asynchronicity, and message delivering.

ipcIO can:

  • Free You from declaring event handlers in callbacks at client or server instantiation,
  • organize communication in tidy command messages,
  • queue client messages when server is unavailable or is being restarted,
  • send messages from client to server,
  • emit messages from server to client or from client to other client,
  • broadcast messages from server or client to all clients in server's domain,
  • deliver messages (send with async receive confirmation and result feedback),
  • show others that I'm valuable and creative programmer ;) .

ipcIO can not:

  • Be used with Windows domain sockets,
  • in general, use socket communication other than Unix domain,
  • so, no TCP, UDP, Web sockets,
  • and it can not write whole app for you (shame on it!).

Table of Contents

Jump-in tutorial

  • Install package:

$ npm install ipcio

  • Create server code, let's name it test-server.js:
const ipcio = require("ipcio");
const exampleServer = new ipcio.Server({
    verbose: true,
    domain: "example_domain",
  },
  {
    example_request_command: (container) => {
      console.log("Example command handler triggered.");
      console.log(`Client name: ${container.name}`);
      console.log(`Receiving data: ${container.data}`);

      // Send command by calling ipcio server instance...
      exampleServer.emit(container.name, "example_response_command", "example response data");
    },
    other_request_command: (container) => {
      console.log("Other command handler triggered.");
      console.log(`Client name: ${container.name}`);
      console.log(`Receiving data: ${container.data}`);

      let clientSocket = container.socket;

      // ...or by calling client socket instance.
      clientSocket.writeCommand("other_response_command", "other response data");
    },
  })
;

exampleServer.start(); // Synchronous, it is client responsibility to poll for connection.
  • Create client code, name file something like test-client.js:
const ipcio = require("ipcio");
const exampleClient = new ipcio.Client({
    verbose: true,
    name: "example_client",
    domain: "example_domain",
  },
  {
    example_response_command: (container) => {
      console.log(`Received example response command with data: ${container.data}`);
    },
    other_response_command: (container) => {
      console.log(`Received other response command with data: ${container.data}`);
    },
  }
);

exampleClient
  .connect()
  .then(() => {
    setInterval(() => {
      exampleClient
        .send("example_request_command", "example request data") // Returns promise.
        .then(() => {
          console.log("Example request command has just been sent.");
        })
      ;
    }, 7331);
    setInterval(() => {
      exampleClient
        .send("other_request_command", "other request data")
        .then(() => {
          console.log("Example request command has just been sent.");
        })
      ;
    }, 1338);

  })
;
  • Watch them run!

$ node test-server $ node test-client

Overview

Because jump-in tutorials show everything but explain nothing, here's the overview of features.

Callback-free setup

When instantiating client or server, instead of being overruled by pattern typical to nodejs connectivity solutions), e.g....

const server = net.createServer((socket) => {

  // On socket creation actions go here.
})
.on("data", (buffer) => {

  // Handle data buffer.
})
;

server.listen(() => {
  console.log("server listening on", server.address());
});

...you can use class-oriented pattern and benefit from its tidiness, like example below:

const exampleServer = new ipcio.Server(

  // First argument is a set of options.
  {
    domain: "example_domain",
  },

  // Second argument to constructor is a handler collection
  // that we want to inject on instantiation.
  // If we need to do it here or not - that depends only on the needs and design.
  {
    example_request_command: (container) => {
      console.log(`Receiving data: ${container.data}`);
      exampleServer.emit(container.name, "example_response_command", "example response data");
    },
  }
);

During time our app does some actions. If there is still no need, server does not have to be started immediately after instantiation. We start our server, when we decide that it is time. Pass server instance anywhere in your code, where you want to start it, and:

if (!exampleServer.isStarted()) {
  exampleServer.start();
}

All handlers registered on instantiation will work from server start. If at some point of time there will be need to add handler for other command, we simply do it.

exampleServer.addHandlers(
  {
    latter_request_command: (container) => {
      console.log(`Receiving data: ${container.data}`);
      exampleServer.emit(container.name, "latter_response_command", "example response data");
    },

    // And others...
  }
);

Working with commands

Command is a message type that ipcIO is using. Commands are sent / emitted / delivered by parties along with data carried with it.

IpcIO message consists of:

  • command name, that is of your invention. It is good when command names are not confusing and briefly describes what they are responsible for. There are some restricted command names however:
[ "handshake", "discover", "broadcast", "emit", "deliver", "error" ]
  • data which can be any JSON serializable data,

  • id which is client name of party that message is addressed to.

Typical communication-responsible method of ipcIO client or server require passing command name and data to be carried arguments. It looks as follows (assuming that client and server are instantiated as client and server):

Server methods (return server instance):

function someServerConnectivity() {
  server

    // Will emit command with carried data to client given "client1" name.
    // Args: client name, command name, data.
    .emit("client1", "commandName1", {
      data: "some_data",
    })

    // Will broadcast command to all clients in server's domain.
    // Args: command name, data.
    .broadcast("commandName1", {
      data: "some_data",
    })
  ;
}

Client methods (return promises):

function someClientConnectivity() {

  // Will emit command with carried data to client given "client1" name.
  // Args: client name, command name, data.
  client.emit("client1", "commandName1", {
      data: "some_data",
    }).then(() => {

	  // "commandName1" is successfully received by server
	  // for further emit to "client1".
    })
  ;

  // Will broadcast command to all clients in server's domain
  // (except this client).
  // Args: command name, data.
  client.broadcast("commandName1", {
      data: "some_data",
    }).then(() => {

	  // "commandName1" is successfully received by server for broadcast.
	  // It does not mean that broadcast action is complete
	  // and client processed the message.
    })
  ;

  // Will get information about command handlers registered to server
  // and clients in server's domain.
  // Args: none.
  client.discover().then((feedback) => {
    console.log(feedback); // {handlers: ["command1"], clients: ["client1"]}
  });

  // Will deliver command with carried data to client given "client1" name
  // and will deliver back delivery confirmation along with request processing
  // feedback.
  // Note: calling without client name, will deliver to the server.
  // Args: client name, command name, data.
  client.deliver("client1", "commandName1", {
      data: "some_data",
    }).then((feedback) => {
      console.log(`We know that delivery is confirmed and we have a ${feedback}.`);
    })
  ;

}

Or, same as above, with async/await:

async function someClientConnectivity() {
  await client.emit("client1", "commandName1", {
    data: "some_data",
  });

  await client.broadcast("commandName1", {
    data: "some_data",
  });

  let discoveryFeedback = await client.discover();
  console.log(discoveryFeedback);

  let deliveryFeedback = await client.deliver("client1", "commandName1", {
    data: "some_data",
  });
  console.log(`We know that delivery is confirmed and we have a ${deliveryFeedback}.`);
}

Client message queue demo

It is possible to emit messages when there is no connection between server and client. Of course, emit will happen once connection is (re)established. By that time, messages are queued on client side. To see how it works, let's follow this example:

  • Instantiate "producer" client and request to connect it to server.
const producerClient = new ipcio.Client({
  name: "producer",
  domain: "test",
});

producerClient.connect().then(() => {
  console.log("Producer client has just been connected.");
});
  • Instantiate server.
const server = new ipcio.Server({
    domain: "test",
});
  • Add handler for "databaseRequest" command to server.
server.addHandlers({
  databaseRequest: (container) => { // {command: function(container)}

    // @typedef {object} container
    // @property {string} data    Message data
    // @property {string} name    Friendly name of client/uuid if name not set.
    // @property {Socket} socket  Instance of net.Socket

    // Pseudo code
    validateRequest(container.data);
    database.query(container.data).then((result) => {

      // Args: client name, command name, data to be emitted
      server.emit(container.name, "databaseResponse", result);
    });
  },
});

As you might probably notice, "databaseRequest" handler calls server to emit "databaseResponse" command.

  • Register handler for "databaseResponse" at "producer" client.
producerClient.addHandlers({
  databaseResponse: (container) => {
    // Pseudo code
    validateResponse(container.data);
    doSomethingWithData();
  },
});
  • Once we have everything set, we can just start sending messages to server.
producerClient.send("databaseRequest", {
  what_kind: "query",
  for_what: "some data from db",
});
  • Start server.
server.start();

As server is started, you can see that all messages are being sent / emitted / delivered.

Well... intentional server start delay does not sound reasonable (except such demos like above, to explain how queueing works), but, queueing becomes handful when we have modules that want to send some information during server module restart. Or, when we have some module that wants to send or receive some data before connectivity is up, and it can wait for it.

Message delivery

Ever wanted to asynchronously receive confirmation that your request has been delivered, most likely with response? Sounds like old commercial, but delivery is exactly what it promises. In order to show delivery in action, let's incorporate working example from message queue demo. All we need is to change, is code of server handler for "databaseRequest", from

server.addHandlers({
  databaseRequest: (container) => {

    // Pseudo code
    validateRequest(container.data);
    database.query(container.data).then((result) => {
      server.emit(container.name, "databaseResponse", result);
    });
  },
});

to

server.addHandlers({
  databaseRequest: (container) => {

    // Pseudo code
    validateRequest(container.data);

    return database.query(container.data);
  },
});

It is enough to return result instead of emitting it back (regardless if we return a promise or some synchronously obtained result). Result of promise will be delivered back, once promise is settled, synchronous result will be delivered back instantly (keeping in mind that connection is established).

In other words, now, when we execute client deliver method:

client.deliver("client1", "databaseRequest", {
  what_kind: "query",
  for_what: "some data from db",
}).then((feedback) => {
  console.log(`We know that delivery is confirmed and we have a ${feedback}.`);
});

API reference

ipcIO.Server

Inter-Process-Communication Server

Kind: static class of ipcIO

new IpcServer(options, handler_collection)

Creates new instance of ipcIO Server.

ParamTypeDescription
optionsserver_constructor_optionsDetermines operating properties and behavior.
handler_collectionhandler_collectionHandlers to be registered at construction time.

Example

const exampleServer = new ipcio.Server({
    verbose: true,             // Determines if actions are reported to console,
    domain: "example_domain",  // domain name,
    encoding: "utf8",          // message encoding.
  },
  {
    example_request_command: (container) => { // {command: function(container)}

      // @typedef {object} container
      // @property {string} data        Message data
      // @property {string} client_name Friendly name of client/uuid if name not set.
      // @property {Socket} socket      Instance of net.Socket
      // @property {Server} server      Instance of net.Server

      // Do handler logic here.
    },
  })
;

server.verbose : boolean

When true, will feed the console.

Kind: instance property of Server

server.isStarted() ⇒ boolean

Checks if server is started.

Kind: instance method of Server
Returns: boolean - True when server is started via IpcIO.Server#start() call.

server.start() ⇒ module:ipcIO.IpcServer

Starts IpcServer instance.

Kind: instance method of Server
Throws:

  • Error If this method was called before and IpcServer instance is already started.

server.addHandlers(handler_collection) ⇒ module:ipcIO.IpcServer

Adds handlers at any time, regardless client state.

Kind: instance method of Server

ParamTypeDescription
handler_collectionhandler_collectionHandlers to be registered.

Example

const exampleServer = new ipcio.Server({
  // Server instantiation options
});

// Some code...

exampleServer.addHandlers({
  example_request_command: (container) => { // {command: function(container)}

    // @typedef {object} container
    // @property {string} data        Message data
    // @property {string} client_name Friendly name of client/uuid if name not set.
    // @property {Socket} socket      Instance of net.Socket

    // Do handler logic here.
  },
});

server.emit(client_name, command, data, delivery) ⇒ module:ipcIO.IpcServer

Writes to client socket of given friendly name.

Kind: instance method of Server

ParamTypeDefaultDescription
client_namestringFriendly name of client.
commandstring | nullCommand description.
datastring | nullData carried by message.
deliverystring | nullnullId of delivery of message that needs to be confirmed with response data. Practically used with COMMAND_DELIVER.

Example

const exampleServer = new ipcio.Server({
  // Server instantiation options
});

// Some code...

exampleServer.emit("example_client", "example_command", {prop1: "prop1"});

server.broadcast(command, data, initiator_client) ⇒ module:ipcIO.IpcServer

Writes to all client sockets within server domain, except initiator client, if provided.

Kind: instance method of Server

ParamTypeDefaultDescription
commandstring | nullCommand description
datastring | nullData carried by message.
initiator_clientstring | nullnullFriendly name of client which initiated broadcast (if client-initiated).

Example

const exampleServer = new ipcio.Server({
  // Server instantiation options
});

// Some code...

exampleServer.broadcast("example_command", {prop1: "prop1"});

ipcIO.Client

Inter-Process-Communication Client

Kind: static class of ipcIO

new IpcClient(options, handler_collection)

Creates new instance of ipcIO Client.

ParamTypeDescription
optionsclient_constructor_optionsDetermines operating properties and behavior.
handler_collectionhandler_collectionHandlers to be registered at construction time.

Example

const exampleClient = new ipcio.Client({
    verbose: true,             // Determines if actions are reported to console,
    name: "example_client",    // friendly client name,
    domain: "example_domain",  // domain name,
    encoding: "utf8",          // message encoding.
  },
  {
    example_request_command: (container) => { // {command: function(container)}

      // @typedef {object} container
      // @property {string} data   Message data
      // @property {string} name   Friendly name of client/uuid if name not set.
      // @property {Socket} socket Instance of net.Socket

      // Do handler logic here.
    },
  })
;

client.verbose : boolean

When true, will feed the console.

Kind: instance property of Client

client.isConnected() ⇒ boolean

Checks if client is connected.

Kind: instance method of Client
Returns: boolean - True when connection is established.

client.isStarted() ⇒ boolean

Checks if client is started by calling IpcIO.Client#connect().

Kind: instance method of Client
Returns: boolean - True when connected or attempting to (re)connect.

client.addHandlers(handler_collection) ⇒ module:ipcIO.IpcClient

Adds handlers at any time, regardless client state.

Kind: instance method of Client

ParamTypeDescription
handler_collectionhandler_collectionHandlers to be registered at construction time.

Example

const exampleClient = new ipcio.Client({
  // Client instantiation options
});

// Some code...

exampleClient.addHandlers({
  example_request_command: (container) => { // {command: function(container)}

    // @typedef {object} container
    // @property {string} data   Message data
    // @property {string} name   Friendly name of client/uuid if name not set.
    // @property {Socket} socket Instance of net.Socket

    // Do handler logic here.
  },
});

client.connect() ⇒ Promise

Connects client to the server.

Kind: instance method of Client
Returns: Promise - Promise for client unique socket connection

client.send(command, data, delivery) ⇒ Promise

Puts command with data queue, calls queue handler. Command is sent immediately to server when there is connection established and previous entries become sent. Returned promise is fulfilled when message was successfully received by server.

Kind: instance method of Client

ParamTypeDefaultDescription
commandstring | nullCommand description
datastring | nullData carried by message
deliverystring | nullnullId of delivery of message that needs to be confirmed with response data. Practically used with COMMAND_DELIVER.

Example

const exampleClient = new ipcio.Client({
  // Client instantiation options
});

// Some code...

exampleClient
  .send("example_command", {prop1: "prop1"})
  .then(() => {
    // Do something when message is successfully sent to server.
  })
;

client.discover() ⇒ Promise

Requests server to expose its command names and friendly names of clients connected to it. Puts command with data to broadcast socket queue, calls queue handler. Command is emitted immediately when there is connection established and previous entries become emitted. Returned promise for server discovery info is fulfilled on discover response from server.

Kind: instance method of Client
Example

const exampleClient = new ipcio.Client({
  // Client instantiation options
});

// Some code...

exampleClient
  .discover()
  .then((result) => {
    console.log(result); // { clients: [ 'example_client' ],
                         // command_handlers: [ 'example_request_command', 'other_request_command' ] }
  })
;

client.broadcast(command, data) ⇒ Promise

Requests server to write to all client sockets within server domain, except this client. Puts command with data to broadcast socket queue, calls queue handler. Command is emitted immediately when there is connection established and previous entries become emitted. Returned promise is fulfilled when message was successfully received by server.

Kind: instance method of Client

ParamTypeDescription
commandstring | nullCommand description
datastring | nullData carried by message.

Example

const exampleClient = new ipcio.Client({
  // Client instantiation options
});

// Some code...

exampleClient
  .broadcast("example_command", {prop1: "prop1"})
  .then(() => {
    // Do something when broadcast is successfully received by server.
  })
;

client.emit(client_name, command, data) ⇒ Promise

Requests server to write command, to client with name given as first argument. Puts command with data to broadcast socket queue, calls queue handler. Command is emitted immediately when there is connection established and previous entries become emitted. Returned promise is fulfilled when message was successfully received by server.

Kind: instance method of Client

ParamTypeDescription
client_namestringFriendly name of client
commandstring | nullCommand description
datastring | nullData carried by message

Example

const exampleClient = new ipcio.Client({
  // Client instantiation options
});

// Some code...

exampleClient
  .emit("example_client", "example_command", {prop1: "prop1"})
  .then(() => {
    // Do something when message is successfully emitted to server (NOT emitted further by server do destination).
  })
;

client.deliver(client_name, command, data) ⇒ Promise

Requests server to write command, to client with name given as first argument. Command write is then confirmed, using reserved deliver command, when command processing is finished on the remote side and delivered back to requester client. When client_name is omitted, deliver is performed to server. Command is emitted immediately when there is connection established and previous entries become emitted. Returned promise is fulfilled when message was successfully received and processed by destination party.

Kind: instance method of Client

ParamTypeDescription
client_namestringFriendly name of client
commandstring | nullCommand description
datastring | nullData carried by message

Example

const exampleClient = new ipcio.Client({
  // Client instantiation options
});

// Some code...

exampleClient
  .deliver("example_client", "example_command", {prop1: "prop1"})
  .then((feedback) => {
    console.log(`We know that delivery is confirmed and we have a ${feedback}.`);
  })
;

Typedefs

ipcIO~parsed_message : object

Object being parsed representation of message.

Kind: inner typedef of ipcIO
Properties

NameTypeDescription
idstring | nullClient id, that server is tagging handshake message with.
commandstring | nullCommand description.
datastring | nullData carried by message.
deliverystring | nullDelivery id

ipcIO~parsed_message_array : Array.<parsed_message>

Array of parsed messages

Kind: inner typedef of ipcIO

ipcIO~iface : object

Interface exposing connectivity to client/server handlers.

Kind: inner typedef of ipcIO
Properties

NameTypeDescription
socketSocketInstance of net.Socket
serverServerInstance of net.Server

ipcIO~handler_container : object

Kind: inner typedef of ipcIO
Properties

NameTypeDescription
datastringMessage data
client_namestringFriendly name of client/uuid if name not set.
socketSocketInstance of net.Socket
serverServerInstance of net.Server

ipcIO~handler_collection : object

Kind: inner typedef of ipcIO
Properties

NameType
commandfunction

ipcIO~server_constructor_options : object

Object containing options that determine behavior of IpcIO.Server

Kind: inner typedef of ipcIO
Properties

NameTypeDescription
verbosebooleanWhen true, will feed console with current operations feedback.
domainstringNamespace used for connection with all clients handshaking with this server.
encodingstringMessage buffer encoding, defaults to "utf8".

ipcIO~client_constructor_options : object

Object containing options that determine behavior of IpcIO.Client

Kind: inner typedef of ipcIO
Properties

NameTypeDescription
verbosebooleanWhen true, will feed console with current operations feedback.
namestringClient friendly name, can be used to address client when emitting from server..
domainstringNamespace used for connection with all clients handshaking with this server.
encodingstringMessage buffer encoding, defaults to "utf8".
0.6.2

7 years ago

0.6.1

7 years ago

0.6.0

7 years ago

0.5.3

7 years ago

0.5.2

7 years ago

0.5.1

7 years ago

0.4.0

7 years ago

0.3.3

7 years ago

0.3.2

7 years ago