Main /
HowToCreateANodeModuleSummaryThis page is a work in progress about the steps necessary to write an module for the Node Host so that we can instantiate Accessors that use the modules. In this example, we create WebSocket Client and Server module implementations in Node.js so that Accessors can load these modules. This page shows how to port a module from Cape Code to Node. TLDRAccessors require modules that are implemented by the hosts.
See
mkAccessorNodeModule
Usage: bash-3.2$ cd $PTII/org/terraswarm/accessor/accessors/web/hosts/node bash-3.2$ ./bin/mkAccessorNodeModule web-socket-client 1. Created /Users/cxh/ptII/org/terraswarm/accessor/accessors/web/hosts/node/node_modules/@accessors-modules/web-socket-client/package.json 2. Created /Users/cxh/ptII/org/terraswarm/accessor/accessors/web/hosts/node/node_modules/@accessors-modules/web-socket-client2/web-socket-client2.j bash-3.2$ ls node_modules/@accessors-modules/web-socket-client package.json web-socket-socket.js bash-3.2$ Details about how the Node host finds modules
When to create an AccessorAccessors are to encapsulate non-trivial bits of functionality that are suitable for reuse. The same holds for modules, but a module may be host-dependent, whereas an accessor may not. For simple functionality (such as a ramp) just use JavaScript. Note that the Cape Code Host can have both JSAccessor actors (which are wrappers for accessors) and JavaScript actors, which can be used for simple functionality. Overview of AccessorsIf you are not yet familiar with Accessors, see Getting Started. In particular, see Publications And Presentations The quotes below are from the web page:
The good news is that:
The less good news is that modules names in the Java-based Cape Code Host and the Node-based Node Host differ:
Another minor issue is that Java does not support
The Big Picture
Files to ModifyWe are adding files that define the Node.js module implementation for web-socket-client and web-socket-server.
Future work:
File ModificationsMake the following edits to
Make the following edits to
FIXME: we need to deal with the EventEmitter Adding the ws moduleWe will be using the ws Node.js interface, so we need to add update var WebSocket = require('ws'); While in the npm install ws This should create We will check the modules in to the svn repo after everything is working. supportedReceiveTypes()In the Cape Code Host, the webSocketClient.js file has this method: /////////////////////////////////////////////////////////////////////////////// //// supportedReceiveTypes /** Return an array of the types supported by the current host for * receiveType arguments. */ exports.supportedReceiveTypes = function () { return WebSocketHelper.supportedReceiveTypes(); }; The above Cape Code Host code will not work for us because it uses the Nashorn Java var bash-3.2$ bash-3.2$ cat Test.java import ptolemy.actor.lib.jjs.modules.webSocket.WebSocketHelper; import java.util.Arrays; public class Test { public static void main (String args []) { System.out.println(Arrays.toString(WebSocketHelper.supportedReceiveTypes())); } } bash-3.2$ javac -classpath $PTII:. Test.java bash-3.2$ $PTII/bin/ptinvoke Test [application/json, text/plain, JPG, jpg, bmp, BMP, gif, GIF, WBMP, png, PNG, jpeg, wbmp, JPEG] bash-3.2$ Thus, method in the Node Host file /////////////////////////////////////////////////////////////////////////////// //// supportedReceiveTypes /** Return an array of the types supported by the current host for * receiveType arguments. */ exports.supportedReceiveTypes = function () { // These values are based on what Cape Code returns in // ptolemy.actor.lib.jjs.modules.webSocket.WebSocketHelper.supportedReceiveTypes(). // Not sure about the validity of 'JPG' and subsequent args. return ['application/json', 'text/plain', 'JPG', 'jpg', 'bmp', 'BMP', 'gif', 'GIF', 'WBMP', 'png', 'PNG', 'jpeg', 'wbmp', 'JPEG']; }; supportedSendTypes()If we update /////////////////////////////////////////////////////////////////////////////// //// supportedSendTypes /** Return an array of the types supported by the current host for * sendType arguments. */ exports.supportedSendTypes = function () { // These values are based on what Cape Code returns in // ptolemy.actor.lib.jjs.modules.webSocket.WebSocketHelper.supportedSendTypes(). // FIXME: Not sure about the validity of 'JPG' and subsequent args. return ['application/json', 'text/plain', 'JPG', 'jpg', 'bmp', 'BMP', 'gif', 'GIF', 'WBMP', 'png', 'PNG', 'jpeg', 'wbmp', 'JPEG']; }; util.inherits()It is good to sanity check our work by parsing the file. Jumping past the Client constructor, there is a line that needs to be commented out so that the Node Host can parse this file. The Cape Code Host includes the following line after the Client constructor: util.inherits(exports.Client, EventEmitter); However, because we have no util module in the Node Host, when we invoke bash-3.2$ node webSocketClient/webSocketClient.js /Users/cxh/ptII/org/terraswarm/accessor/accessors/web/{$HOSTS_NODE}/webSocketClient/webSocketClient.js:175 util.inherits(exports.Client, EventEmitter); ^ ReferenceError: util is not defined at Object.<anonymous> (/Users/cxh/ptII/org/terraswarm/accessor/accessors/web/{$HOSTS_NODE}/webSocketClient/webSocketClient.js:175:1) at Module._compile (module.js:413:34) at Object.Module._extensions..js (module.js:422:10) at Module.load (module.js:357:32) at Function.Module._load (module.js:314:12) at Function.Module.runMain (module.js:447:10) at startup (node.js:141:18) at node.js:933:3 bash-3.2$ FIXME: Not sure what to do here. Do we define a Solution var util = require('util'); In the short term, we comment it out // FIXME: Not sure what to do with util.inherits. // See {$ACCESSORS_HOME}/wiki/Main/HowToCreateANodeAccessor#utilInherits //util.inherits(exports.Client, EventEmitter); Does it parse?As a quick test, we run node on the module file to see if it parses: bash-3.2$ pwd /Users/cxh/ptII/org/terraswarm/accessor/accessors/web/{$HOSTS_NODE} bash-3.2$ node node_modules/webSocketClient/webSocketClient.js bash-3.2$ The above was successful, there were no parse errors. Composite Accessor testNext up, we create a composite Accessor that contains a WebSocketClient and try to run it.
Success! We can instantiate a WebSocket accessor using our module. However, it does not actually do anything. Client constructor.Next up in the Cape Code implementation is the declaration of a Client constructor: /////////////////////////////////////////////////////////////////////////////// //// Client /** Construct an instance of a socket client that can send or receive messages * to a server at the specified host and port. * The returned object subclasses EventEmitter. * You can register handlers for events 'open', 'message', 'close', or 'error'. * The event 'open' will be emitted when the socket has been successfully opened. * The event 'message' will be emitted with the body of the message as an * argument when an incoming message arrives on the socket. * You can invoke the send() function to send data to the server. * * The type of data sent and received can be specified with the 'sendType' * and 'receiveType' options. * In principle, any MIME type can be specified, but the host may support only * a subset of MIME types. The client and the server have to agree on the type, * or the data will not get through correctly. * * The default type for both sending and receiving * is 'application/json'. The types supported by this implementation * include at least: * * __application/json__: The this.send() function uses JSON.stringify() and sends the * result with a UTF-8 encoding. An incoming byte stream will be parsed as JSON, * and if the parsing fails, will be provided as a string interpretation of the byte * stream. * * __text/\*__: Any text type is sent as a string encoded in UTF-8. * * __image/x__: Where __x__ is one of __json__, __png__, __gif__, * and more (FIXME: which, exactly?). * In this case, the data passed to this.send() is assumed to be an image, as encoded * on the host, and the image will be encoded as a byte stream in the specified * format before sending. A received byte stream will be decoded as an image, * if possible. FIXME: What happens if decoding fails? * * The event 'close' will be emitted when the socket is closed, and 'error' if an * an error occurs (with an error message as an argument). * For example, * <pre> * var WebSocket = require('webSocketClient'); * var client = new WebSocket.Client({'host': 'localhost', 'port': 8080}); * client.send({'foo': 'bar'}); * client.on('message', function(message) { * console.log('Received from web socket: ' + message); * }); * client.open(); * </pre> * * The above code may send a message even before the socket is opened. This module * implementation will queue that message to be sent later when the socket is opened. * * The options argument is a JSON object that can contain the following properties: * * host: The IP address or host name for the host. Defaults to 'localhost'. * * port: The port on which the host is listening. Defaults to 80. * * receiveType: The MIME type for incoming messages, which defaults to 'application/json'. * * sendType: The MIME type for outgoing messages, which defaults to 'application/json'. * * connectTimeout: The time to wait before giving up on a connection, in milliseconds * (defaults to 1000). * * numberOfRetries: The number of times to retry connecting. Defaults to 10. * * timeBetweenRetries: The time between retries, in milliseconds. Defaults to 500. * * discardMessagesBeforeOpen: If true, discard messages before the socket is open. Defaults to false. * * throttleFactor: The number milliseconds to stall for each item that is queued waiting to be sent. Defaults to 0. * * @param options The options. */ exports.Client = function (options) { options = options || {}; this.port = options.port || 80; this.host = options.host || 'localhost'; this.sslTls = options.sslTls || false; this.receiveType = options.receiveType || 'application/json'; this.sendType = options.sendType || 'application/json'; this.connectTimeout = options.connectTimeout || 1000; this.numberOfRetries = options.numberOfRetries || 10; this.timeBetweenRetries = options.timeBetweenRetries || 500; this.trustAll = options.trustAll || false; this.trustedCACertPath = options.trustedCACertPath || ''; this.discardMessagesBeforeOpen = options.discardMessagesBeforeOpen || false; this.throttleFactor = options.throttleFactor || 0; this.helper = WebSocketHelper.createClientSocket( this, this.host, this.sslTls, this.port, this.receiveType, this.sendType, this.connectTimeout, this.numberOfRetries, this.timeBetweenRetries, this.trustAll, this.trustedCACertPath, this.discardMessagesBeforeOpen, this.throttleFactor); }; The above needs to be updated to be a call to the appropriate See the ws WebSocket documentation for how to create a WebSocket, which is the closest to the Cape Code WebSocketHelper createClientSocket().
See Also |