ActionHero is very close to the v9.0.0 release! I’m particularly proud of some of the mature features we have added, including a real REPL and RPC tools. Check out the impressive list of changes below.
If you have any comments on the release, please note them on the mailing list. Thanks!
This release focuses on performance, the chat system, and developer tools. We have been listening to your thoughts in the mailing list and on GitHub, and hopefully this release clears up a lot of the confusion and pain points you have identified!
In v9.0.0, the chat system has been gutted and re-written to provide your API with finer controls about chat rooms. Most importantly, you can now control which rooms connections are members of directly. Connections can continue opt to join and leave rooms on their own (assuming the authenticationPattern is met).
In this release, we have removed our dependency on faye in favor of Primus. We now use Primus in the websocket transport, and have moved all backend cluster-cluster communication to raw redis Pub/Sub.
The Primus project allows you to choose from many webscoket backends, including ws, engine.io, socket.io, and more. A number of new options have been added to /config/servers/websocket.js to manage this. Check out the Primus project for more information.
actionhero will no longer attempt to manage non-sticky client connections. This means if you have a multi-server actionhero deployment and you use long-polling in your websocket transport, you will need to ensure that your load balancer can enforce sticky connections, meaning every request from the client will hit the same actionhero node.
Implementation Notes
1// you can pass a FQDN here, or function to be called / window object to be inspected 2clientUrl: 'window.location.origin', 3// Directory to render client-side JS. 4// Path should start with "/" and will be built starting from api.config..general.paths.public 5clientJsPath: 'javascript/', 6// the name of the client-side JS file to render. Both `.js` and `.min.js` versions will be created 7// do not include the file exension 8// set to `null` to not render the client-side JS on boot 9clientJsName: 'actionheroClient',
To enable the new chat API above, a key feature was the ability to add connections to a room using "serverA"’a API, even though the connection in question might not be connected to "serverB". This required the creation of a robust Remote Procedure Call (RPC) to allow actionhero servers to communicate with each other.
You can call an RPC to be called on all nodes you may have in your cluster or just a node which holds a specific connection. You can call RPC methods with the new api.redis.doCluster method. If you provide the optional callback, you will get the first response back (or a timeout error). RPC calls are invoked with api.redis.doCluster(method, args, connectionId, callback).
For example, if you wanted all nodes to log a message, you would do: api.redis.doCluster(‘api.log’, ["hello from " + api.id]);
If you wanted the node which holds connection abc123 to change their authorized status (perhaps because your room authentication relies on this), you would do:
1api.connections.apply("abc123", "set", ["auth", true], function (err) { 2 // do stuff 3});
Two new options have been added to the config/redis.js config file to support this:
1// Which channel to use on redis pub/sub for RPC communication 2channel: 'actionhero', 3// How long to wait for an RPC call before considering it a failure 4rpcTimeout: 5000,
RPC calls are authenticated against api.config.serverToken and communication happens over redis Pub/Sub. BE CAREFUL, as you can call any method within the API namespace on an actionhero server, including shutdown() and read any data on that node.
The new api.connections.apply(connectionId, method, args, callback) has been introduced. This allows any node in the cluster to modify a property of a connection, even one that isn’t located locally on this specific node. This uses the RPC tooling described above under the hood.
Thanks to @innerdvations, you can now choose how to order the execution of your middleware (preProcessor and postProcessor, and connection callbacks). You should no longer push to those arrays (although your application won’t error). You should now use api.actions.addPreProcessor(function, priority) and api.actions.addPostProcessor(function, priority) for actions and api.connections.addCreateCallback(function, priority) and api.connections.addDestroyCallback(function, priority) for connections.
The priority in all the above is optional, and if not provided, the new api.config.general.defaultProcessorPriority will be used (defaults to 100).
Per a discussion on the mailing list, we have removed any automatic messaging actionhero might do for the chatrooms in favor of another type of middleware, chat middleware! This middleware allows you to control the messages and actions taken when clients join or leave a chat room.
This should not be used for authentication.
As we do not want to block the ability for a connection to join a room (we already have authentication tools in place), Chat Middleware does not have a callback and is executed "in parallel" to the connection actually joining the room. This middleware can be used for announcing members joining and leaving to other members in the chat room or logging stats.
Use api.chatRoom.addJoinCallback(function(connection, room)) to add a Join Callback, and use api.chatRoom.addLeaveCallback(function(connection, room) to handle connections leaving a room.
You can optionally provide a priority to control the order of operations in the middleware.
You can announce to everyone else in the room when a connection joins and leaves:
1api.chatRoom.addJoinCallback(function (connection, room) { 2 api.chatRoom.broadcast(connection, room, "I have entered the room"); 3}); 4 5api.chatRoom.addLeaveCallback(function (connection, room) { 6 api.chatRoom.broadcast(connection, room, "I have left the room"); 7});
actionhero now has a REPL! This means you can ‘connect’ to a running instance of actionhero and manually call all the methods on the api namespace. This combined with the new RPC tools make this a powerful debugging and development tool. Running grunt console will load up a version of action hero in your terminal where you have access to the api object. This version of the server will boot, initialize, and start, but will skip booting any servers.
The REPL will:
If you are familiar with rails, this is very similar to rails console
Many of you have asked for the ability to change the string error messages actionhero uses. Perhaps english isn’t your user’s language, or you want so say something custom. Either way, there’s a new config file just for this: config/errors.js. Each error message is represented by a synchronous function which should return a string. Some functions are passed variables (like the connection) so you can customize your message.
Over the past few months, a great conversation has been happening on GitHub about actionhero speed & performance. This conversation has lead to a few small tweaks inside actionhero which have made a big difference. Most importantly, somewhere between v7.0.0. and v8.0.2 we changed the async-ness of the actionProcessor and cache system to rely on setImmediate rather than process.nextTick. This change made the seder less susceptible to crashing under heavy load, but cost us significantly in speed. This change was too costly and has since been reverted.
Thank you to everyone who contributed to the conversation!
The change to Priums not only allows for more flexibility in the websocket server, but in preliminary tests, preforms much better than faye.
A list of things to watch out for when upgrading to v9.0.0 form v8.x.x:
Originally published at 06 Jun 2014
I write about Technology, Software, and Startups. I use my Product Management, Software Engineering, and Leadership skills to build teams that create world-class digital products.
Get in touch