Skip to content

Instantly share code, notes, and snippets.

@aikar
Forked from roder/client.html
Created December 9, 2010 20:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aikar/735236 to your computer and use it in GitHub Desktop.
Save aikar/735236 to your computer and use it in GitHub Desktop.
<html>
<head></head>
<body>
<p><textarea cols=100 rows=25 id="output"></textarea></p>
</body>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
var client = new function() {
var lastid=0;
var _poll = function(lastid) {
$.get('/foo?client=1&chan=7&last='+lastid, function(response) {
if(response.length > 0){
$('textarea').text(response);
lastid = JSON.parse(response).reverse()[0].id;
}
_poll(lastid); // using reverse to get the latest head ID
});
}
// Inital request
$.get('/foo?client=1&chan=7&last='+lastid, function(response) {
$('textarea').text(response);
lastid = JSON.parse(response).reverse()[0].id;
_poll(lastid);
});
}
</script>
</html>
config = module.exports = {
port : 8000,
redis: {
host: '127.0.0.1',
port: 6379
},
};
/**
* MultiServ
*
* Enables running a TCP Socket server over multiple processes to enable using multiple cores on a system
* Written by Daniel Ennis (Aikar) <Aikar@Aikar.co>
* License: WTFPL
**/
(function()
{
var net = require('net');
var self = __filename;
var env = process.env;
if(!env.__IS_CHILD)
{
var spawn = require('child_process').spawn;
var path = require('path');
var util = require('util');
var netBinding = process.binding('net');
multiserv = module.exports = function(app, numWorkers, options)
{
var port = options.port || 80;
var ip = options.ip || '0.0.0.0';
var workers = [];
var env = {};
var shuttingDown = false;
for(var i in process.env)
{
env[i] = process.env[i];
}
["SIGINT", "SIGTERM", "SIGKILL", "SIGQUIT", "SIGHUP", "exit"].forEach(function(signal)
{
shuttingDown = true;
process.addListener(signal, function(signal)
{
workers.forEach(function(worker)
{
if(signal != 'exit') worker.kill(signal);
worker._stdin.end();
worker._stdout.end();
});
if(signal != 'exit') process.reallyExit();
});
});
env.__IS_CHILD = true;
var i = 0;
var spawnWorker = function()
{
var stdin = netBinding.socketpair();
var stdout = netBinding.socketpair();
var stderr = netBinding.socketpair();
var worker = spawn(process.execPath, [self, app], {
env: env,
customFds: [stdin[1], stdout[1], stderr[1]]
});
worker.sendFD = function(fd)
{
worker._stdin.write('fd', 'utf8', fd);
};
workers.push(worker);
worker.i = i;
worker._stdin = new net.Stream(stdin[0],'unix');
worker._stdin.resume();
worker._stdout = new net.Stream(stdout[0],'unix');
worker._stdout.on('data', function(data)
{
var index = workers.indexOf(worker);
console.log('[worker:' + worker.i + ']<< ' + data.toString().replace(/^\s*/, "").replace(/\s*$/, "") + '>>');
});
worker._stdout.resume();
worker._stderr = new net.Stream(stderr[0],'unix');
worker._stderr.on('data', function(data)
{
var index = workers.indexOf(worker);
console.log('[error worker:' + worker.i + ']<< ' + data.toString().replace(/^\s*/, "").replace(/\s*$/, "") + '>>');
});
worker._stderr.resume();
worker.on('exit', function(code)
{
var index = workers.indexOf(worker);
if(!shuttingDown) console.log('worker',worker.i,' has died')
if(index >= 0)
{
workers.splice(index, 1);
}
if(!code && !shuttingDown)
{
spawnWorker();
}
});
//console.log('Workers:', workers.length, workers);
}
for(var i = 0; i < numWorkers; i++)
{
spawnWorker();
}
if(workers.length)
{
var defaultHandler = function(conn, workers)
{
if(workers.length)
{
conn.pause();
var hv = 0;
conn.remoteAddress.split('.').forEach(function(v)
{
hv += parseInt(v);
});
var i = hv % workers.length;
//console.log('got conn on i', i, workers.length, workers);
workers[i].sendFD(conn.fd);
}else{
conn.end();
}
}
var handler = options.handler || defaultHandler;
net.createServer(function(conn)
{
handler(conn, workers);
}).listen(port, ip);
}
return workers;
}
}
else
{
if(process.argv[2])
{
var serv = require(process.argv[2]);
var stdin = new net.Stream(0, 'unix');
["SIGINT", "SIGTERM", "SIGKILL", "SIGQUIT", "SIGHUP", "exit"].forEach(function(signal)
{
process.addListener(signal, function()
{
stdin.end();
if(signal != 'exit') process.reallyExit();
});
});
stdin.on('data', function(data){
//console.log('client got data', data);
});
stdin.on('fd', function(fd)
{
//console.log('got fd');
var client = new net.Stream(fd, 'unix');
client.type = serv.type;
client.server = serv;
["SIGINT", "SIGTERM", "SIGKILL", "SIGQUIT", "SIGHUP", "exit"].forEach(function(signal)
{
process.addListener(signal, function()
{
client.end();
});
});
client.resume();
serv.emit('connection', client);
});
stdin.resume();
}
}
})();
var conf = require('./config'),
multiserv = require('./multiserv');
multiserv(require.resolve('./toyservernode'), 8, {port: conf.port});
(function(){
var http = require('http'),
url = require('url'),
fs = require('fs'),
conf = require('./config'),
redis = require('redis');
var Chan = function(id){
var pubsub = redis.createClient(conf['redis']['port'], conf['redis']['host']);
var db = redis.createClient(conf['redis']['port'], conf['redis']['host']);
// A note on IDs. I use incrementing ints in the toy, but uuids in the syncpad server
var ID = 0;
var alterObj = function(obj){
if(obj.eggs){
delete obj.spam;
}
return obj;
};
var pool = [];
pubsub.on('message', function(channel, record){
var remove = [];
pool.forEach(function(val){
var obj = JSON.parse(record);
if(val.clientId != obj.client){
obj = alterObj(obj);
// now back to a string for the client
val.response.write(JSON.stringify([obj]));
remove.push(val);
val.response.end();
}
});
remove.forEach(function(val)
{
var idx = pool.indexOf(val);
if(idx >= 0) pool.splice(idx, 1);
});
});
var startClient = function(response, clientId){
var entry = {
clientId:clientId, response:response
};
pool.push(entry);
setTimeout(function() {
var idx = pool.indexOf(entry);
if(idx >= 0) pool.splice(idx, 1);
response.end();
}, 15000);
}
pubsub.subscribe(id);
return {
id:id,
foo: function(last, clientId, response){
db.lrange(id, 0, 500, function(err, records){
try{
if(records != null){
// In the syncpad server code, this actually maps 1000 elements, which could account for better performance in the toy server
var l = records.map(JSON.parse);
var curr = l[0].id;
if (last != curr){
var objList = new Array();
for (var i=0; i<l.length; i++){
if(last == l[i].id){
objList = objList.map(alterObj);
response.write(JSON.stringify(objList));
break;
} else {
objList.unshift(l[i]);
}
}
response.end();
} else {
startClient(response, clientId);
}
} else {
startClient(response,clientId);
}
} catch(error){
response.end();
}
});
return;
},
bar: function(query, response){
try{
var obj = {
id: ID++,
foo: query.foo,
bar: query.bar,
baz: query.baz,
spam: query.spam,
eggs: query.eggs,
client: query.client
};
var value = JSON.stringify(obj);
db.publish(id, value);
db.lpush(id, value);
response.writeHead(201, {'Content-Type': 'text/plain'});
}catch(error){
response.writeHead(400, {'Content-Type': 'text/plain'});
}
response.end();
return;
},
close: function(){
db.end();
pubsub.unsubscribe();
pubsub.end();
return;
}
}
}
var ToyServer = function(){
var _self = this;
var chans = {};
var handleFoo = function(req, res){
// sample relative URI : "/foo?chan=7&client=1&last=4"
var query = url.parse(req.url,true).query;
var last = query.last;
var id = query.chan;
var clientId = query.client;
if (chans[id] === undefined){
chans[id] = new Chan(id)
}
chan = chans[id];
res.writeHead(200, {'Content-Type': 'text/plain'});
chan.foo(last, clientId, res);
};
var handleBar = function(req, res){
// sample relative URI "/bar?foo=1&bar=2&baz=3&spam=4&eggs=5&client=6&chan=7"
var query = url.parse(req.url,true).query;
var id = query.chan;
if (chans[id] === undefined){
chans[id] = new Chan(id)
}
chan = chans[id];
chan.bar(query, res);
};
var routes = {
'/foo': handleFoo,
'/bar': handleBar
};
var _requestHandler = function(request, response) {
if(routes[url.parse(request.url).pathname] === undefined) {
response.writeHead(404, {'Content-Type': 'text/plain'});
response.write('HTTP 404: Not Found\n');
response.end();
return;
} else {
routes[url.parse(request.url).pathname].call(this, request, response);
}
};
module.exports = http.createServer().addListener('request', _requestHandler)
};
new ToyServer();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment