actionhero
Version:
actionhero.js is a multi-transport API Server with integrated cluster capabilities and delayed tasks
197 lines (156 loc) • 8 kB
Markdown
## Overview
```bash
> telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
{"welcome":"Hello! Welcome to the actionhero api","room":"defaultRoom","context":"api"}
$ detailsView
{"status":"OK","context":"response","data":{"id":"2d68c389-521d-4dc6-b4f1-8292cd6cbde6","remoteIP":"127.0.0.1","remotePort":57393,"params":{},"connectedAt":1368918901456,"room":"defaultRoom","totalActions":0,"pendingActions":0},"messageCount":1}
randomNumber
{"randomNumber":0.4977603426668793,"context":"response","messageCount":2}
$ cacheTest
{"error":"Error: key is a required parameter for this action","context":"response","messageCount":3}
$ paramAdd key=myKey
{"status":"OK","context":"response","data":null,"messageCount":4}
$ paramAdd value=myValue
{"status":"OK","context":"response","data":null,"messageCount":5}
$ paramsView
{"status":"OK","context":"response","data":{"action":"cacheTest","key":"myKey","value":"myValue"},"messageCount":6}
$ cacheTest
{"cacheTestResults":{"saveResp":true,"sizeResp":1,"loadResp":{"key":"cacheTest_myKey","value":"myValue","expireTimestamp":1368918936984,"createdAt":1368918931984,"readAt":1368918931995},"deleteResp":true},"context":"response","messageCount":7}
$ roomAdd default Room
{"status":"OK"}
$ say defaultRoom hooray!
{"status":"OK","context":"response","data":null,"messageCount":8}
```
You can access actionhero's methods via a persistent socket connection. The default port for this type of communication is 5000\. As this is a persistent connection, socket connections have actionhero's verbs available to them. These verbs are:
* `quit` disconnect from the session
* `paramAdd` - save a singe variable to your connection. IE: ‘addParam screenName=evan'
* `paramView` - returns the details of a single param. IE: ‘viewParam screenName'
* `paramDelete` - deletes a single param. IE: `deleteParam screenName`
* `paramsView` - returns a JSON object of all the params set to this connection
* `paramsDelete` - deletes all params set to this session
* `roomAdd` - connect to a room.
* `roomLeave` - (room) leave the `room` you are connected to.
* `roomView` - (room) show you the room you are connected to, and information about the members currently in that room.
* `detailsView` - show you details about your connection, including your public ID.
* `say` (room,) message
Please note that any verbs set using the above method will be sticky to the connection and sent for all subsequent requests. Be sure to delete or update your params before your next request.
To help sort out the potential stream of messages a socket user may receive, it is best to understand the "context" of the response. For example, by default all actions set a context of "response" indicating that the message being sent to the client is response to a request they sent (either an action or a chat action like `say`). Messages sent by a user via the `say` command have the context of `user` indicating they came form a user. Messages resulting from data sent to the api (like an action) will have the `response` context.
`connection.type` for a TCP/Socket client is "socket"
## Config Options
```js
exports['default'] = {
servers: {
socket: function (api) {
return {
enabled: (process.env.ENABLE_TCP_SERVER !== undefined),
// TCP or TLS?
secure: false,
// Passed to tls.createServer if secure=true. Should contain SSL certificates
serverOptions: {},
// Port or Socket
port: 5000,
// Which IP to listen on (use 0.0.0.0 for all)
bindIP: '0.0.0.0',
// Enable TCP KeepAlive pings on each connection?
setKeepAlive: false,
// Delimiter string for incoming messages
delimiter: '\n',
// Maximum incoming message string length in Bytes (use 0 for Infinite)
maxDataLength: 0
}
}
}
}
```
## TLS Encryption
```js
config.severs.socket = {
// TCP or TLS?
secure: true,
// Passed to tls.createServer if secure=true. Should contain SSL certificates
serverOptions: {
key: fs.readFileSync('certs/server-key.pem'),
cert: fs.readFileSync('certs/server-cert.pem')
}
};
```
You can switch your TCP server to use TLS encryption if you desire. Just toggle the settings in `/config/servers/socket.js` and provide valid certificates. You can test this with the openSSL client rather than telnet `openssl s_client -connect 127.0.0.1:5000`
Note that if you wish to create a secure (tls) server, you will be required to complete the serverOptions hash with at least a cert and a keyfile:
You can connect like: `openssl s_client -connect 127.0.0.1:5000`
or from node:
```js
// Connecting over TLS from another node process
var tls = require('tls');
var fs = require('fs');
var options = {
key: fs.readFileSync('certs/server-key.pem'),
cert: fs.readFileSync('certs/server-cert.pem')
};
var cleartextStream = tls.connect(5000, options, function() {
console.log('client connected', cleartextStream.authorized ? 'authorized' : 'unauthorized');
process.stdin.pipe(cleartextStream);
process.stdin.resume();
});
cleartextStream.setEncoding('utf8');
cleartextStream.on('data', function(data) {
console.log(data);
});
```
## Files and Routes
Connections over socket can also use the file action. There is no route for files.
* Errors are returned in the normal way `{`{error: someError}`}` when they exist.
* A successful file transfer will return the raw file data in a single send(). There will be no headers set, not will the content be JSON.
## JSON Parameters
The default method of using actions for TCP clients is to use the methods above to set params to their session and then call actions inline. However, you can also communication via JSON, passing along params specific to each request.
* `{`{"action": "myAction", "params": {"key": "value"}}`}` is also a valid request over TCP
## Client Suggestions
```js
var actionheroClient = require("actionhero-client");
var client = new actionheroClient();
client.on("say", function(msgBlock){
console.log(" > SAY: " + msgBlock.message + " | from: " + msgBlock.from);
});
client.on("welcome", function(msg){
console.log("WELCOME: " + msg);
});
client.on("error", function(error, data){
console.log("ERROR: " + error);
if(data){ console.log(data); }
});
client.on("end", function(){
console.log("Connection Ended");
});
client.on("timeout", function(error, request, caller){
console.log(request + " timed out");
});
client.connect({
host: "127.0.0.1",
port: "5000",
}, function(){
// get details about myself
console.log(client.details);
// try an action
var params = { key: "mykey", value: "myValue" };
client.actionWithParams("cacheTest", params, function(error, apiResponse, delta){
console.log("cacheTest action response: " + apiResponse.cacheTestResults.saveResp);
console.log(" ~ request duration: " + delta + "ms");
});
// join a chat room and talk
client.roomAdd("defaultRoom", function(error){
client.say("defaultRoom", "Hello from the actionheroClient");
client.roomLeave("defaultRoom");
});
// leave
setTimeout(function(){
client.disconnect(function(){
console.log("all done!");
});
}, 1000);
});
```
The main `trick` to working with TCP/wire connections directly is to remember that you can have many ‘pending' requests at the same time. Also, the order in which you receive responses back can be variable. if you request `slowAction` and then `fastAction`, it's fairly likely that you will get a response to `fastAction` first.
Note that only requests the client makes increment the `messageCount`, but broadcasts do not (the `say` command, etc)
[The actionhero client library](https://github.com/actionhero/actionhero-client) uses TCP/TLS connections, and makes use of actionhero's `messageCount` parameter to keep track of requests, and keeps response callbacks for actions in a pending queue. For example: