APE (Ajax Push Engine) is an extraordinary Client/Server Framework for creating AJAX PUSH (Comet) applications. In the latest revisions on github there is even the use of HTML5´s Websockets implemented, which makes the standard way of implementing Comet by using a "hanging iframe" obsolete (for browsers with websocket support). Also the APE server can be scripted by using JavaScript which makes it very easy to extend it´s features (at least for developers with JS knowledge). The default framework used is mootools, but any other or plain JS could do the same.
One of APE´s nice features is to invoke messages to the client by a script or application on the server side or even on a different server. This is very usefull when you want to broadcast certain server events or messages from other applications to the Clients connected to APE.
They way how to do this is is register a certain server command, which then excutes the appropriate logic. The command then can be called by the invoking script through HTTP (Depending on your needs a socket connection would also be possible). In APE´s demo section an example for such a feature can be found in the "Controller" Demo using a simple PHP script. (If you need an OOP style Client Library to connect to APE through PHP you may want to check my APE_PHP on github)
However in the Controller demo every message sent by the external script is broadcasted to every user connected to the certain channel. While this is usefull for a lot of cases, there might be the need to send a message to only one specific user on the channel. There is a simple solution for this problem:
One could use a different channel per user and send the message through it. But this approach has (at least) two drawbacks. One is scalability, the other is security. Since the way this is done could be easily found out on the server side a "bad user" could connect to the other user´s channel and receive all of his private messages. This post has been inspired by a discussion on APE´s mailing list on how to implement such feature in a secure way.
In this scenario we assume that there is some kind of server side authentication process (It wouldn't make much sense otherwise anyway) . So when the user is authenticated a secret key is generated or the session key or something else is used. This key will be send to the client with the page where the APE connection will be established.
On the server side we create a hook for the "connect" command and register the key as a private property on the user object.
- Ape.registerHookCmd("connect", function(params, cmd) {
- // this makes the key a private property on the user object
- cmd.ser.key = params.key
- return 1;
- });
Now we create our custom APE command to be called from the script:
- Ape.registerCmd("securepush", false, function(params, infos) {
- if (params.password == "foobar") {
- if ($defined(params.channel) && $defined(params.key)){
- var channel = Ape.getChannelByName(params.channel);
- if (!$defined(channel)) return ["401", "UNKNOWN_CHANNEL"];
- var sent = false;
- channel.userslist.each(function(user){
- if(user.key == params.key){
- user.pipe.sendRaw(params.raw, params.data);
- sent = true;
- }
- });
- return sent ? {"name":"pushed","data":{"value":"ok"}} : ["400", "USER_NOT_FOUND"];
- }
- return ["400", "BAD_KEY"];
- } else {
- return ["400", "BAD_PASSWORD"];
- }
- });
The Client then connects to APE using the special key:
- var client = new APE.Client();
- client.load();
- client.addEvent('load', function() {
- client.core.start({"name": 'UserName', "key":"secretKey"});
- client.addEvent('ready', function() {
- client.core.join('SecureChannel');
- }
- });
Now we can send a message (with the secret key)through our script to the user and only he will receive our message (Example in PHP):
- $APEserver = 'http://ape.example.com:6969/?';
- $APEPassword = 'foobar';
- 'cmd' => 'securepush',
- 'params' => array(
- 'password' => $APEPassword,
- 'raw' => 'postmsg',
- 'channel' => 'SecureChannel',
- 'key' => 'secretKey',
- 'data' => array(
- 'message' => 'You have a new message!'
- )
- )
- ));
- $res = array_pop(json_decode(file_get_contents($APEserver.rawurlencode(json_encode($cmd)))));
- if ($res->data->value == 'ok') {
- echo '<br />Message sent! result: ';
- } else {
- echo 'Error sending message, server response is : ';
- }
- echo '<pre>'.print_r($res, 1).'</pre>';
This is the most simple solution on how to do secure pushs to APE. However it still has (atleast) one drawback, that it uses "channel.userslist.each(function(user){[...]});" which doesn´t scale with a lot of users. I hope I will have the time to post a solution using a proper registry and a way to reconnect with usernames (the default would response with "NICK_USED" if a client would refresh the page).