scriptbox
Version:
Script box is a full VAS application
415 lines (354 loc) • 11.3 kB
JavaScript
/* Simple Memcached in Javascript
* @Author: Sun, Junyi
* @Email: ccnusjy@gmail.com
* @Date: 2012-8-3
*/
var tcp = require('net'),
os = require("os"),
path= require("path"),
emitter = require('events').EventEmitter;
var store = {};
util = require('util');
var crlf = "\r\n";
var crlf_len = crlf.length;
var error_replies = ['ERROR', 'NOT_FOUND', 'CLIENT_ERROR', 'SERVER_ERROR'];
String.prototype.explode = function(separator, limit) {
var arr = this.split(separator);
if (limit) arr.push( arr.splice(limit-1).join(separator) );
return arr;
}
// trouver contrui un tableau si ca n'existe pas
function getStoreData(key, store,val){
key = key.explode('.',2);
console.log("0",key,JSON.stringify(store));
if(store[key[0]] === undefined && key[1]!=="")
store[key[0]] = {};
else if(key[1]==="")
store[key[0]] = val ? val : undefined;
console.log("I",key,key.length,JSON.stringify(store));
if(key[1] === "")
return store;
key2 = key[1].explode('.',2);
console.log("II",key2,JSON.stringify(store[key[0]]));
if(key2[1] !== "")
store[key[0]][key2[0]] = getStoreData(key[1],store[key[0]],val);
else if(val)
store[key[0]][key2[0]] = val;
return store;
}
function handle_header(header,crlf_len){
var tup = header.split(" ")
var expect_body_len = 0
switch(tup[0]){
case 'get':
case 'delete':
expect_body_len = 0
break
case 'set':
expect_body_len = parseInt(tup[4]) + crlf_len
break
}
return expect_body_len
}
function handle_body(socket,header,body,call_back){
var response=""
var tup = header.split(" ")
var l = null;
switch(tup[0]){
case 'get':
var key = tup[1]
var obj = store[key]
if(obj){
response = "VALUE "+ obj.key+" " + obj.flag+" " + obj.data.length + "\r\n"
response += obj.data
response += crlf
response += "END"
response += crlf
}
else
response = error_replies[1]+crlf
break;
case 'delete':
var key = tup[1]
delete store[key]
response = "DELETED"+crlf
break;
case 'add':
l = store[obj.key] ? parseFloat(store[obj.key].data) : 0;
l = isNaN(l) ? 0 : l
case 'incr':
if(l===null) l =1;
case 'decr':
if(l===null) l =-1;
console.log(tup,l);
var obj = {key: tup[1], flag: tup[2]}
var val = store[obj.key] ? parseFloat(store[obj.key].data) : 0
obj.data = (isNaN(val) ? 0:val) + l ;
store[obj.key] = obj
response = "STORED"+crlf
break;
case 'set':
var obj = {key: tup[1], flag: tup[2], data: body}
store[obj.key] = obj
response = "STORED"+crlf
break;
default:
response = error_replies[0]+crlf
break;
}
socket.write(response,"binary",call_back)
}
var server = tcp.createServer(function (socket) {
console.log("client: ",socket.remoteAddress)
var user_state = 'reading_header'
var buf = ""
var header =""
var body = ""
var expect_body_len = 0
var CRLF_LEN = 2
socket.setEncoding("binary")
socket.on('data',function(data){
buf += data
socket.emit('user_event')
})
socket.on('user_event',function(){
switch(user_state){
case "reading_header":
var pos =-1
if((pos=buf.indexOf(crlf))!=-1){
header = buf.slice(0,pos)
buf = buf.slice(pos+crlf_len)
CRLF_LEN =2
}
else if((pos=buf.indexOf('\n'))!=-1){
header = buf.slice(0,pos)
buf = buf.slice(pos+1)
CRLF_LEN =1
}
if(pos!=-1){
user_state = 'reading_body'
expect_body_len = handle_header(header,CRLF_LEN)
socket.emit("user_event")
}
break
case "reading_body":
if(expect_body_len <= buf.length){
body = buf.slice(0,expect_body_len-CRLF_LEN)
buf = buf.slice(expect_body_len)
handle_body(socket,header,body,
function(){
user_state = 'reading_header'
if(buf.length>0)
socket.emit("user_event")
}
)
}
break
}
})
});
//var port = 11211
//console.log("listening at "+ port)
//server.listen(port, '0.0.0.0')
if (process.platform === 'win32')
exports.PIPE = '\\\\.\\pipe\\memory-sock';
else {
exports.PIPE = path.resolve(os.tmpdir() , 'memory.sock');
try{
if (require('fs').existsSync(exports.PIPE))
require('fs').unlinkSync(exports.PIPE);
}catch(e){}
}
var settings = require(path.resolve(__dirname,"settings.json"));
exports.PIPE = parseInt(settings.httpPort || 13014)+10;
exports.server = function(){
server.listen(exports.PIPE );
console.log("Memory server start ",exports.PIPE);
};
var Client = exports.Client = function(scope) {
this.pipe = exports.PIPE;
this.buffer = "";
this.scope = scope ? scope +"::" : "";
this.conn = null;
this.sends = 0;
this.replies = 0;
this.callbacks = [];
this.handles = [];
};
util.inherits(Client, emitter);
Client.prototype.connect = function () {
if (!this.conn) {
this.conn = new tcp.createConnection(this.pipe);
var self = this;
this.conn.addListener("connect", function () {
this.setTimeout(0); // try to stay connected.
this.setNoDelay();
self.emit("connect");
self.dispatchHandles();
});
this.conn.addListener("data", function (data) {
self.buffer += data;
// util.debug(data);
self.recieves += 1;
self.handle_received_data();
});
this.conn.addListener("end", function () {
if (self.conn && self.conn.readyState) {
self.conn.end();
self.conn = null;
}
});
this.conn.addListener("close", function () {
self.conn = null;
self.emit("close");
});
this.conn.addListener("timeout", function () {
self.conn = null;
self.emit("timeout");
});
this.conn.addListener("error", function (ex) {
self.conn = null;
self.emit("error", ex);
});
}
};
Client.prototype.addHandler = function(callback) {
this.handles.push(callback);
if (this.conn.readyState == 'open') {
this.dispatchHandles();
}
};
Client.prototype.dispatchHandles = function() {
for (var i in this.handles) {
var handle = this.handles.shift();
// util.debug('dispatching handle ' + handle);
if (typeof handle !== 'undefined') {
handle();
}
}
};
Client.prototype.query = function(query, type, callback) {
this.callbacks.push({ type: type, fun: callback });
this.sends++;
this.conn.write(query + crlf);
};
Client.prototype.close = function() {
if (this.conn && this.conn.readyState === "open") {
this.conn.end();
this.conn = null;
}
};
Client.prototype.get = function(key, callback) {
return this.query('get ' + this.scope+key, 'get', callback);
};
// all of these store ops (everything bu "cas") have the same format
Client.prototype.set = function(key, value, callback, lifetime, flags) { return this.store('set', key, value, callback, lifetime, flags); }
Client.prototype.add = function(key, value, callback, lifetime, flags) { return this.store('add', key, value, callback, lifetime, flags); }
Client.prototype.replace = function(key, value, callback, lifetime, flags) { return this.store('replace', key, value, callback, lifetime, flags); }
Client.prototype.append = function(key, value, callback, lifetime, flags) { return this.store('append', key, value, callback, lifetime, flags); }
Client.prototype.prepend = function(key, value, callback, lifetime, flags) { return this.store('prepend', key, value, callback, lifetime, flags); }
Client.prototype.store = function(cmd, key, value, callback, lifetime, flags) {
if (typeof(callback) != 'function') {
lifetime = callback;
callback = null;
}
var set_flags = flags || 0;
var exp_time = lifetime || 0;
var tml_buf = new Buffer(value.toString());
var value_len = tml_buf.length || 0;
var query = [cmd, this.scope+key, set_flags, exp_time, value_len];
return this.query(query.join(' ') + crlf + value, 'simple', callback);
};
Client.prototype.delete = function(key, callback){
return this.query('delete ' + this.scope+key, 'simple', callback);
};
Client.prototype.increment = function(key, value, callback) {
if (typeof(value) == 'function') {
callback = value;
value = 1;;
}
value = value || 1;
return this.query('incr ' + this.scope+key + ' ' + value, 'simple', callback);
};
Client.prototype.decrement = function(key, value, callback) {
if (typeof(value) == 'function') {
callback = value;
value = 1;;
}
value = value || 1;
return this.query('decr ' + this.scope+key + ' ' + value, 'simple', callback);
};
Client.prototype.handle_received_data = function(){
while (this.buffer.length > 0){
var result = this.determine_reply_handler(this.buffer);
if (result == null){
break;
}
var result_value = result[0];
var next_result_at = result[1];
var result_error = result[2];
// does the current message need more data than we have?
// (this is how "get" ops ensure we've gotten all the data)
if (next_result_at > this.buffer.length){
break;
}
this.buffer = this.buffer.substring(next_result_at);
var callback = this.callbacks.shift();
if (callback != null && callback.fun){
this.replies++;
callback.fun(result_error, result_value);
}
}
};
Client.prototype.determine_reply_handler = function (buffer){
// check we have a whole line in the buffer
var crlf_at = buffer.indexOf(crlf);
if (crlf_at == -1){
return null;
}
// determine errors
for (var error_idx in error_replies){
var error_indicator = error_replies[error_idx];
if (buffer.indexOf(error_indicator) == 0) {
return this.handle_error(buffer);
}
}
// call the handler for the current message type
var type = this.callbacks[0].type;
if (type){
return this['handle_' + type](buffer);
}
return null;
};
Client.prototype.handle_get = function(buffer) {
var next_result_at = 0;
var result_value = null;
var end_indicator_len = 3;
var result_len = 0;
if (buffer.indexOf('END') == 0) {
return [result_value, end_indicator_len + crlf_len];
} else if (buffer.indexOf('VALUE') == 0 && buffer.indexOf('END') != -1) {
first_line_len = buffer.indexOf(crlf) + crlf_len;
var end_indicator_start = buffer.indexOf('END');
result_len = end_indicator_start - first_line_len - crlf_len;
result_value = buffer.substr(first_line_len, result_len);
return [result_value, first_line_len + parseInt(result_len, 10) + crlf_len + end_indicator_len + crlf_len]
} else {
var first_line_len = buffer.indexOf(crlf) + crlf_len;
var result_len = buffer.substr(0, first_line_len).split(' ')[3];
result_value = buffer.substr(first_line_len, result_len);
return [result_value, first_line_len + parseInt(result_len ) + crlf_len + end_indicator_len + crlf_len];
}
};
Client.prototype.handle_simple = function(buffer){
var line = readLine(buffer);
return [line, (line.length + crlf_len), null];
};
Client.prototype.handle_error = function(buffer){
var line = readLine(buffer);
return [null, (line.length + crlf_len), line];
};
readLine = function(string){
var line_len = string.indexOf(crlf);
return string.substr(0, line_len);
};