diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a64f639 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2011 Voxeo Corporation + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..9fe3dd7 --- /dev/null +++ b/readme.md @@ -0,0 +1,29 @@ +Overview +======== + +A simple application demonstrating the power of combining Tropo, Redis, Node.js and jQuery. + +Usage +===== + +You'll need to following to run this example: + +* A Tropo account (Create one for free or login at [Tropo.com](http://tropo.com/). +* An instance of Redis that external sources can connect to - [Redis to Go](https://redistogo.com/) is a good way to get this piece. +* A server running Node.js - this example was tested with v0.4.3. +* A webserver to serve the file socket.html (the HTML + jQuery page in this solution). + +You will need to modify the files "tropo-redis.php" and "socket.js" to add the settings for your Reids instance. +Create a new Tropo Scripting application and use the "tropo-redis.php" file as the source file for the application. +Launch the file "socket.js" using Node.js (by default, this server will listen on localhost port 8000, change this if needed): + + node path/to/socket.js + +If you are not running your socket.js server on localhost at port 8000, you will need to modify the file "socket.html" to point to your socket.io instance. +Once your socket.js server is running, load the socket.html page in your web browser. +You should now be able to call into your Tropo application (via the Skype number auto provisioned for your app, or by adding a phone number). + +Feedback +======== + +Feedback is appreciated. Send to mheadd [at] voxeo [dot] com. \ No newline at end of file diff --git a/redis.php b/redis.php new file mode 100644 index 0000000..36931bf --- /dev/null +++ b/redis.php @@ -0,0 +1,163 @@ +host = $host; + $this->port = $port; + } + + /** + * + * Connect to a Redis server. + */ + public function connect() { + if ($this->sock) + return; + if ($sock = fsockopen($this->host, $this->port, $errno, $errstr)) { + $this->sock = $sock; + return; + } + $msg = "Cannot open socket to {$this->host}:{$this->port}"; + if ($errno || $errmsg) { + $msg .= "," . ($errno ? " error $errno" : "") . ($errmsg ? " $errmsg" : ""); + } + trigger_error("$msg.", E_USER_ERROR); + } + + /** + * + * Set authentication credentials + * @param string $password + */ + public function auth($password) { + $this->auth = $password; + } + + /** + * + * Disconnect from a Redis server. + */ + public function disconnect() { + if ($this->sock) + @fclose($this->sock); + $this->sock = null; + } + + /** + * + * Publish a message on a channel. + * @param string $channel + * @param string $message + */ + public function publish($channel, $message) { + + // Connect to the server. + $this->connect(); + + // Authenticate. + if($this->auth) { + $this->doAuth($this->auth); + } + + // Publish message. + $this->write("PUBLISH $channel $message\r\n"); + return $this->get_response(); + + } + + /** + * + * Private methods. + * + */ + + private function doAuth($auth) { + $this->write("AUTH $auth\r\n"); + } + + private function write($s) { + while ($s) { + $i = fwrite($this->sock, $s); + if ($i == 0) // || $i == strlen($s)) + break; + $s = substr($s, $i); + } + } + + private function read($len=1024) { + if ($s = fgets($this->sock)) + return $s; + $this->disconnect(); + trigger_error("Cannot read from socket.", E_USER_ERROR); + } + + private function get_response() { + $data = trim($this->read()); + $c = $data[0]; + $data = substr($data, 1); + switch ($c) { + case '-': + trigger_error(substr($data, 0, 4) == 'ERR ' ? substr($data, 4) : $data, E_USER_ERROR); + break; + case '+': + return $data; + case '*': + $num = (int)$data; + if ((string)$num != $data) + trigger_error("Cannot convert multi-response header '$data' to integer", E_USER_ERROR); + $result = array(); + for ($i=0; $i<$num; $i++) + $result[] =& $this-get_value(); + return $result; + default: + return $this->get_value($c . $data); + } + } + + private function get_value($data=null) { + if ($data === null) + $data =& trim($this->read()); + if ($data == '$-1') + return null; + $c = $data[0]; + $data = substr($data, 1); + $i = strpos($data, '.') !== false ? (int)$data : (float)$data; + if ((string)$i != $data) + trigger_error("Cannot convert data '$c$data' to integer", E_USER_ERROR); + if ($c == ':') + return $i; + if ($c != '$') + trigger_error("Unkown response prefix for '$c$data'", E_USER_ERROR); + $buffer = ''; + while (true) { + $data =& $this->read(); + $i -= strlen($data); + $buffer .= $data; + if ($i < 0) + break; + } + return substr($buffer, 0, -2); + } + +} + +?> diff --git a/socket.html b/socket.html new file mode 100644 index 0000000..b1a77b4 --- /dev/null +++ b/socket.html @@ -0,0 +1,62 @@ + + + + + + + + + +

Redis PubSub + Socket.io Test

+
+ + diff --git a/socket.js b/socket.js new file mode 100644 index 0000000..8902ce4 --- /dev/null +++ b/socket.js @@ -0,0 +1,52 @@ +/* + * Socket IO server / Redis client. + * Allows connections from HTML page ans sets up a subscriber client on a Redis instance. + * + */ + +// Include require modules. +var io = require('socket.io'); +var redis = require('redis'); +var http = require('http'); +var sys = require('sys'); + +// Redis config settings. +var redisHost = ''; +var redisPort = 6379; +var redisPass = ''; + +// Create a web server for Socket IO to use. +var server = http.createServer(function(req, res){ + res.writeHead(200, {'Content-Type': 'text/html'}); + res.end('

This is a node web server.

'); +}); + +// Specify the port the server should listen on. +server.listen(8000); + +// Create a new socket +var socket = io.listen(server); + +// Listen for connection from clients. +socket.on('connection', function(client){ + + // A client has connected. + sys.puts('Got a new connection!'); + + // Create a Redis client and subscribe. + var redisClient = redis.createClient(redisPort, redisHost); + redisClient.auth(redisPass); + redisClient.subscribe("tropo.color"); + + // Handler for Redis client subscription. + redisClient.on("subscribe", function (channel) { + sys.puts("Redis Client Subscribed to " + channel); + }); + + // When a message comes in on the subscribed channel, send it to the connected web client. + redisClient.on("message", function (channel, message) { + sys.puts("redisClient channel " + channel + ": message " + message); + client.send(message); + }); + +}); diff --git a/tropo-redis.php b/tropo-redis.php new file mode 100644 index 0000000..b376504 --- /dev/null +++ b/tropo-redis.php @@ -0,0 +1,156 @@ +connect(); +$redis->auth(REDIS_PASS); + +say("Welcome to the Redis and socket I O test."); + +do { + + $result = ask("Say the color you want to see. When done, say stop.", array("choices" => "white, blue, green, yellow, stop")); + _log("*** User selected: ".$result->value." ***"); + $response = $redis->publish("tropo.color", $result->value); + +} while ($result->value != 'stop'); + +$redis->disconnect(); +say("Goodbye."); +hangup(); + +class Redis { + + // Private class members. + private $server; + private $port; + private $sock; + private $auth; + + function __construct($host='localhost', $port=6379) { + $this->host = $host; + $this->port = $port; + } + + public function connect() { + if ($this->sock) + return; + if ($sock = fsockopen($this->host, $this->port, $errno, $errstr)) { + $this->sock = $sock; + return; + } + $msg = "Cannot open socket to {$this->host}:{$this->port}"; + if ($errno || $errmsg) { + $msg .= "," . ($errno ? " error $errno" : "") . ($errmsg ? " $errmsg" : ""); + } + trigger_error("$msg.", E_USER_ERROR); + } + + public function auth($password) { + $this->auth = $password; + } + + public function disconnect() { + if ($this->sock) + @fclose($this->sock); + $this->sock = null; + } + + public function publish($channel, $message) { + + // Connect to the server. + $this->connect(); + + // Authenticate. + if($this->auth) { + $this->doAuth($this->auth); + } + + // Publish message. + $this->write("PUBLISH $channel $message\r\n"); + return $this->get_response(); + + } + + private function doAuth($auth) { + $this->write("AUTH $auth\r\n"); + } + + private function write($s) { + while ($s) { + $i = fwrite($this->sock, $s); + if ($i == 0) // || $i == strlen($s)) + break; + $s = substr($s, $i); + } + } + + private function read($len=1024) { + if ($s = fgets($this->sock)) + return $s; + $this->disconnect(); + trigger_error("Cannot read from socket.", E_USER_ERROR); + } + + private function get_response() { + $data = trim($this->read()); + $c = $data[0]; + $data = substr($data, 1); + switch ($c) { + case '-': + trigger_error(substr($data, 0, 4) == 'ERR ' ? substr($data, 4) : $data, E_USER_ERROR); + break; + case '+': + return $data; + case '*': + $num = (int)$data; + if ((string)$num != $data) + trigger_error("Cannot convert multi-response header '$data' to integer", E_USER_ERROR); + $result = array(); + for ($i=0; $i<$num; $i++) + $result[] =& $this-get_value(); + return $result; + default: + return $this->get_value($c . $data); + } + } + + private function get_value($data=null) { + if ($data === null) + $data =& trim($this->read()); + if ($data == '$-1') + return null; + $c = $data[0]; + $data = substr($data, 1); + $i = strpos($data, '.') !== false ? (int)$data : (float)$data; + if ((string)$i != $data) + trigger_error("Cannot convert data '$c$data' to integer", E_USER_ERROR); + if ($c == ':') + return $i; + if ($c != '$') + trigger_error("Unkown response prefix for '$c$data'", E_USER_ERROR); + $buffer = ''; + while (true) { + $data =& $this->read(); + $i -= strlen($data); + $buffer .= $data; + if ($i < 0) + break; + } + return substr($buffer, 0, -2); + } + +} + + +?> \ No newline at end of file