PageRenderTime 59ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/source/com/codeazur/as3redis/Redis.as

https://github.com/lvshiling/as3redis
ActionScript | 628 lines | 459 code | 112 blank | 57 comment | 52 complexity | 6d41adfc6a86f93f6f995f7d7f7b653c MD5 | raw file
  1. package com.codeazur.as3redis
  2. {
  3. import com.codeazur.as3redis.commands.*;
  4. import com.codeazur.as3redis.events.RedisMonitorDataEvent;
  5. import flash.display.Sprite;
  6. import flash.events.Event;
  7. import flash.events.EventDispatcher;
  8. import flash.events.IOErrorEvent;
  9. import flash.events.ProgressEvent;
  10. import flash.events.SecurityErrorEvent;
  11. import flash.net.Socket;
  12. import flash.utils.ByteArray;
  13. import flash.utils.getTimer;
  14. public class Redis extends EventDispatcher
  15. {
  16. protected var socket:Socket;
  17. protected var idleQueue:Vector.<RedisCommand>;
  18. protected var activeQueue:Vector.<RedisCommand>;
  19. protected var buffer:ByteArray;
  20. protected var active:Boolean = false;
  21. protected var connecting:Boolean = false;
  22. protected var connectResultHandler:Function;
  23. protected var enterFrameProvider:Sprite;
  24. protected var _host:String;
  25. protected var _port:int;
  26. protected var _password:String;
  27. protected var _immediateSend:Boolean = true;
  28. public function Redis(host:String = "127.0.0.1", port:int = 6379)
  29. {
  30. _host = host;
  31. _port = port;
  32. idleQueue = new Vector.<RedisCommand>();
  33. activeQueue = new Vector.<RedisCommand>();
  34. buffer = new ByteArray();
  35. enterFrameProvider = new Sprite();
  36. socket = new Socket();
  37. socket.addEventListener(Event.CONNECT, connectHandler);
  38. socket.addEventListener(ProgressEvent.SOCKET_DATA, dataHandler);
  39. socket.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
  40. socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler);
  41. }
  42. public function get password():String { return _password; }
  43. public function set password(value:String):void { _password = value; }
  44. public function get immediateSend():Boolean { return _immediateSend; }
  45. public function set immediateSend(value:Boolean):void { _immediateSend = value; }
  46. public function get connected():Boolean { return socket.connected; }
  47. public function connect(host:String = "127.0.0.1", port:int = 6379):void {
  48. connectInternal(host, port);
  49. }
  50. public function flush():void {
  51. executeIdleCommands();
  52. }
  53. // Connection handling
  54. public function sendQUIT():RedisCommand {
  55. return addCommand(new QUIT());
  56. }
  57. public function sendAUTH(password:String):RedisCommand {
  58. return addCommand(new AUTH(password));
  59. }
  60. // Commands operating on string values
  61. public function sendSET(key:String, value:*):RedisCommand {
  62. return addCommand(new SET(key, value));
  63. }
  64. public function sendGET(key:String):RedisCommand {
  65. return addCommand(new GET(key));
  66. }
  67. public function sendGETSET(key:String, value:*):RedisCommand {
  68. return addCommand(new GETSET(key, value));
  69. }
  70. public function sendMGET(keys:Array):RedisCommand {
  71. return addCommand(new MGET(keys));
  72. }
  73. public function sendSETNX(key:String, value:*):RedisCommand {
  74. return addCommand(new SETNX(key, value));
  75. }
  76. public function sendINCR(key:String):RedisCommand {
  77. return addCommand(new INCR(key));
  78. }
  79. public function sendINCRBY(key:String, value:uint):RedisCommand {
  80. return addCommand(new INCRBY(key, value));
  81. }
  82. public function sendDECR(key:String):RedisCommand {
  83. return addCommand(new DECR(key));
  84. }
  85. public function sendDECRBY(key:String, value:uint):RedisCommand {
  86. return addCommand(new DECRBY(key, value));
  87. }
  88. public function sendEXISTS(key:String):RedisCommand {
  89. return addCommand(new EXISTS(key));
  90. }
  91. public function sendDEL(keys:Array):RedisCommand {
  92. return addCommand(new DEL(keys));
  93. }
  94. public function sendTYPE(key:String):RedisCommand {
  95. return addCommand(new TYPE(key));
  96. }
  97. // Commands operating on the key space
  98. public function sendKEYS(pattern:String):RedisCommand {
  99. return addCommand(new KEYS(pattern));
  100. }
  101. public function sendRANDOMKEY():RedisCommand {
  102. return addCommand(new RANDOMKEY());
  103. }
  104. public function sendRENAME(oldKey:String, newKey:String):RedisCommand {
  105. return addCommand(new RENAME(oldKey, newKey));
  106. }
  107. public function sendRENAMENX(oldKey:String, newKey:String):RedisCommand {
  108. return addCommand(new RENAMENX(oldKey, newKey));
  109. }
  110. public function sendDBSIZE():RedisCommand {
  111. return addCommand(new DBSIZE());
  112. }
  113. public function sendEXPIRE(key:String, seconds:uint):RedisCommand {
  114. return addCommand(new EXPIRE(key, seconds));
  115. }
  116. public function sendTTL(key:String):RedisCommand {
  117. return addCommand(new TTL(key));
  118. }
  119. // Version 1.1
  120. public function sendMSET(keys:Array, values:Array):RedisCommand {
  121. return addCommand(new MSET(keys, values));
  122. }
  123. // Version 1.1
  124. public function sendMSETNX(keys:Array, values:Array):RedisCommand {
  125. return addCommand(new MSETNX(keys, values));
  126. }
  127. // Commands operating on lists
  128. public function sendRPUSH(key:String, value:*):RedisCommand {
  129. return addCommand(new RPUSH(key, value));
  130. }
  131. public function sendLPUSH(key:String, value:*):RedisCommand {
  132. return addCommand(new LPUSH(key, value));
  133. }
  134. public function sendLLEN(key:String):RedisCommand {
  135. return addCommand(new LLEN(key));
  136. }
  137. public function sendLRANGE(key:String, startIndex:int, endIndex:int):RedisCommand {
  138. return addCommand(new LRANGE(key, startIndex, endIndex));
  139. }
  140. public function sendLTRIM(key:String, startIndex:int, endIndex:int):RedisCommand {
  141. return addCommand(new LTRIM(key, startIndex, endIndex));
  142. }
  143. public function sendLINDEX(key:String, index:int):RedisCommand {
  144. return addCommand(new LINDEX(key, index));
  145. }
  146. public function sendLSET(key:String, index:int, value:*):RedisCommand {
  147. return addCommand(new LSET(key, index, value));
  148. }
  149. public function sendLREM(key:String, count:int, value:*):RedisCommand {
  150. return addCommand(new LREM(key, count, value));
  151. }
  152. public function sendLPOP(key:String):RedisCommand {
  153. return addCommand(new LPOP(key));
  154. }
  155. public function sendRPOP(key:String):RedisCommand {
  156. return addCommand(new RPOP(key));
  157. }
  158. // Version 1.1
  159. public function sendRPOPLPUSH(sourceKey:String, destinationKey:String):RedisCommand {
  160. return addCommand(new RPOPLPUSH(sourceKey, destinationKey));
  161. }
  162. // Commands operating on sets
  163. public function sendSADD(key:String, value:*):RedisCommand {
  164. return addCommand(new SADD(key, value));
  165. }
  166. public function sendSREM(key:String, value:*):RedisCommand {
  167. return addCommand(new SREM(key, value));
  168. }
  169. public function sendSPOP(key:String):RedisCommand {
  170. return addCommand(new SPOP(key));
  171. }
  172. public function sendSMOVE(sourceKey:String, destinationKey:String, value:*):RedisCommand {
  173. return addCommand(new SMOVE(sourceKey, destinationKey, value));
  174. }
  175. public function sendSCARD(key:String):RedisCommand {
  176. return addCommand(new SCARD(key));
  177. }
  178. public function sendSISMEMBER(key:String, value:*):RedisCommand {
  179. return addCommand(new SISMEMBER(key, value));
  180. }
  181. public function sendSINTER(keys:Array):RedisCommand {
  182. return addCommand(new SINTER(keys));
  183. }
  184. public function sendSINTERSTORE(destinationKey:String, keys:Array):RedisCommand {
  185. return addCommand(new SINTERSTORE(destinationKey, keys));
  186. }
  187. public function sendSUNION(keys:Array):RedisCommand {
  188. return addCommand(new SUNION(keys));
  189. }
  190. public function sendSUNIONSTORE(destinationKey:String, keys:Array):RedisCommand {
  191. return addCommand(new SUNIONSTORE(destinationKey, keys));
  192. }
  193. public function sendSDIFF(keys:Array):RedisCommand {
  194. return addCommand(new SDIFF(keys));
  195. }
  196. public function sendSDIFFSTORE(destinationKey:String, keys:Array):RedisCommand {
  197. return addCommand(new SDIFFSTORE(destinationKey, keys));
  198. }
  199. public function sendSMEMBERS(key:String):RedisCommand {
  200. return addCommand(new SMEMBERS(key));
  201. }
  202. // Version 1.1
  203. public function sendSRANDMEMBER(key:String):RedisCommand {
  204. return addCommand(new SRANDMEMBER(key));
  205. }
  206. // Version 1.1
  207. // Commands operating on sorted sets (zsets)
  208. public function sendZADD(key:String, score:Number, value:*):RedisCommand {
  209. return addCommand(new ZADD(key, score, value));
  210. }
  211. public function sendZREM(key:String, value:*):RedisCommand {
  212. return addCommand(new ZREM(key, value));
  213. }
  214. public function sendZRANGE(key:String, startIndex:int, endIndex:int):RedisCommand {
  215. return addCommand(new ZRANGE(key, startIndex, endIndex));
  216. }
  217. public function sendZREVRANGE(key:String, startIndex:int, endIndex:int):RedisCommand {
  218. return addCommand(new ZREVRANGE(key, startIndex, endIndex));
  219. }
  220. public function sendZRANGEBYSCORE(key:String, minScore:Number, maxScore:Number):RedisCommand {
  221. return addCommand(new ZRANGEBYSCORE(key, minScore, maxScore));
  222. }
  223. public function sendZCARD(key:String):RedisCommand {
  224. return addCommand(new ZCARD(key));
  225. }
  226. public function sendZSCORE(key:String, value:*):RedisCommand {
  227. return addCommand(new ZSCORE(key, value));
  228. }
  229. // Multiple databases handling commands
  230. public function sendSELECT(dbIndex:uint):RedisCommand {
  231. return addCommand(new SELECT(dbIndex));
  232. }
  233. public function sendMOVE(key:String, dbIndex:uint):RedisCommand {
  234. return addCommand(new MOVE(key, dbIndex));
  235. }
  236. public function sendFLUSHDB():RedisCommand {
  237. return addCommand(new FLUSHDB());
  238. }
  239. public function sendFLUSHALL():RedisCommand {
  240. return addCommand(new FLUSHALL());
  241. }
  242. // Sorting
  243. public function sendSORT(key:String, limitMin:int = -1, limitMax:int = -1, desc:Boolean = false, alpha:Boolean = false, byPattern:String = null, getPatterns:Array = null):RedisCommand {
  244. return addCommand(new SORT(key, limitMin, limitMax, desc, alpha, byPattern, getPatterns));
  245. }
  246. // Persistence control commands
  247. public function sendSAVE():RedisCommand {
  248. return addCommand(new SAVE());
  249. }
  250. public function sendBGSAVE():RedisCommand {
  251. return addCommand(new BGSAVE());
  252. }
  253. public function sendLASTSAVE():RedisCommand {
  254. return addCommand(new LASTSAVE());
  255. }
  256. public function sendSHUTDOWN():RedisCommand {
  257. return addCommand(new SHUTDOWN());
  258. }
  259. // Remote server control commands
  260. public function sendINFO():RedisCommand {
  261. return addCommand(new INFO());
  262. }
  263. public function sendMONITOR():RedisCommand {
  264. return addCommand(new MONITOR());
  265. }
  266. public function sendSLAVEOF(host:String = null, port:int = -1):RedisCommand {
  267. return addCommand(new SLAVEOF(host, port));
  268. }
  269. // Misc commands (undocumented)
  270. public function sendPING():RedisCommand {
  271. return addCommand(new PING());
  272. }
  273. public function sendECHO(text:String):RedisCommand {
  274. return addCommand(new ECHO(text));
  275. }
  276. protected function addCommand(command:RedisCommand):RedisCommand {
  277. idleQueue.push(command);
  278. executeIdleCommands();
  279. return command;
  280. }
  281. protected function executeIdleCommands():void {
  282. if (!active) {
  283. if (!connected) {
  284. if (!connecting) {
  285. connectInternal(_host, _port, executeIdleCommands);
  286. }
  287. } else {
  288. enterFrameProvider.addEventListener(Event.ENTER_FRAME, executeIdleCommandsRunner);
  289. active = true;
  290. }
  291. }
  292. }
  293. protected function executeIdleCommandsRunner(event:Event):void {
  294. var startTime:Number = getTimer();
  295. var command:RedisCommand;
  296. while(idleQueue.length > 0) {
  297. if(getTimer() - startTime < 20) {
  298. command = idleQueue.shift();
  299. command.send(socket);
  300. activeQueue.push(command);
  301. } else {
  302. break;
  303. }
  304. }
  305. socket.flush();
  306. if(idleQueue.length == 0) {
  307. enterFrameProvider.removeEventListener(Event.ENTER_FRAME, executeIdleCommandsRunner);
  308. active = false;
  309. }
  310. }
  311. protected function connectInternal(host:String, port:int, resultHandler:Function = null):void {
  312. _host = host;
  313. _port = port;
  314. connecting = true;
  315. connectResultHandler = resultHandler;
  316. socket.connect(host, port);
  317. }
  318. protected function connectHandler(e:Event):void {
  319. connecting = false;
  320. // Redispatch the event
  321. dispatchEvent(e.clone());
  322. // Authentication
  323. if (_password) {
  324. // A password is set, so we have to send the AUTH command first
  325. idleQueue.splice(0, 0, new AUTH(_password));
  326. }
  327. if (connectResultHandler != null) {
  328. connectResultHandler();
  329. }
  330. }
  331. protected function errorHandler(e:Event):void {
  332. // Redispatch the event
  333. dispatchEvent(e.clone());
  334. }
  335. protected function dataHandler(e:ProgressEvent):void {
  336. // Read all available bytes from the socket and append them to the buffer
  337. socket.readBytes(buffer, buffer.length, socket.bytesAvailable);
  338. // Parse buffer from the start
  339. buffer.position = 0;
  340. var commandProcessed:Boolean = true;
  341. while (commandProcessed && buffer.length - buffer.position >= 3) {
  342. var pos:uint = buffer.position;
  343. // Find the next CR/LF pair starting from the current position
  344. var i:int = findCRLF(buffer, buffer.position);
  345. if (i > 0) {
  346. // We found a CR/LF, and there is data available to parse
  347. // Find the first active command in the queue
  348. var command:RedisCommand = activeQueue.shift();
  349. if (command != null) {
  350. var len:int;
  351. // The first byte of a redis response is always the type indicator
  352. var type:String = String.fromCharCode(buffer.readUnsignedByte());
  353. // Followed by the rest, which is interpreted as a string
  354. var head:String = buffer.readUTFBytes(i - buffer.position);
  355. // Followed by the CR/LF we found above
  356. buffer.position += 2; // skip crlf
  357. // So let's see what we're dealing with:
  358. switch(type) {
  359. case "-":
  360. // This is an error message
  361. command.setResponseType(RedisCommand.RESPONSE_TYPE_ERROR);
  362. command.setResponseMessage(head);
  363. command.fault();
  364. break;
  365. case "+":
  366. // This is a single line reply
  367. command.setResponseType(RedisCommand.RESPONSE_TYPE_STRING);
  368. command.setResponseMessage(head);
  369. command.result();
  370. break;
  371. case ":":
  372. // This is an integer number
  373. command.setResponseType(RedisCommand.RESPONSE_TYPE_INTEGER);
  374. command.setResponseMessage(head);
  375. command.result();
  376. break;
  377. case "$":
  378. // This is bulk data
  379. // Get the size of the data block
  380. command.removeAllBulkResponses();
  381. len = parseInt(head);
  382. if (len >= 0) {
  383. // Check if the entire data block is loaded already
  384. if (buffer.length - buffer.position - len - 2 >= 0) {
  385. // Yes it is, so parse and save it
  386. command.addBulkResponse(parseBulk(len));
  387. command.setResponseType(RedisCommand.RESPONSE_TYPE_BULK);
  388. command.result();
  389. } else {
  390. // No, we need to wait for more data
  391. // Set the position back to the beginning of the current response
  392. buffer.position = pos;
  393. commandProcessed = false;
  394. }
  395. } else {
  396. // Length can be -1 (no data available, non-existant key etc)
  397. command.setResponseType(RedisCommand.RESPONSE_TYPE_BULK);
  398. command.setResponseMessage(head);
  399. command.result();
  400. }
  401. break;
  402. case "*":
  403. // This is multi bulk data
  404. command.removeAllBulkResponses();
  405. var count:int = parseInt(head);
  406. if(count > 0) {
  407. for (var j:uint = 0; j < count; j++) {
  408. var nextcrlf:int = findCRLF(buffer, buffer.position);
  409. if (nextcrlf >= 0) {
  410. if (nextcrlf - buffer.position > 1) {
  411. // The first byte of a redis response is always the type indicator
  412. type = String.fromCharCode(buffer.readUnsignedByte());
  413. // Followed by the rest, which is interpreted as a string
  414. head = buffer.readUTFBytes(nextcrlf - buffer.position);
  415. // Followed the CR/LF we found above
  416. buffer.position += 2; // skip crlf
  417. // Response type must be bulk data
  418. if (type == "$") {
  419. len = parseInt(head);
  420. if (len >= 0) {
  421. // Check if the entire data block is loaded already
  422. if (buffer.length - buffer.position - len - 2 >= 0) {
  423. // Yes it is, so parse and save it
  424. command.addBulkResponse(parseBulk(len));
  425. } else {
  426. // No, we need to wait for more data
  427. // Set the position back to the beginning of the current response
  428. buffer.position = pos;
  429. commandProcessed = false;
  430. break;
  431. }
  432. } else {
  433. // Length can be -1 (no data available, non-existant key etc)
  434. command.addBulkResponse(null);
  435. }
  436. } else {
  437. throw(new Error("Illegal header type '" + type + "'."));
  438. }
  439. } else {
  440. throw(new Error("Empty header."));
  441. }
  442. } else {
  443. buffer.position = pos;
  444. commandProcessed = false;
  445. break;
  446. }
  447. }
  448. }
  449. if (commandProcessed) {
  450. command.setResponseType(RedisCommand.RESPONSE_TYPE_BULK_MULTI);
  451. command.result();
  452. }
  453. break;
  454. default:
  455. if(command is MONITOR) {
  456. var event:RedisMonitorDataEvent = new RedisMonitorDataEvent(
  457. RedisMonitorDataEvent.MONITOR_DATA,
  458. type + head
  459. );
  460. dispatchEvent(event);
  461. } else {
  462. throw(new Error("Illegal header type '" + type + "'."));
  463. }
  464. break;
  465. }
  466. if (!commandProcessed || (command is MONITOR)) {
  467. // add command back to queue
  468. activeQueue.splice(0, 0, command);
  469. }
  470. } else {
  471. throw(new Error("No active commands found."));
  472. }
  473. } else if (i == 0) {
  474. throw(new Error("Empty header."));
  475. }
  476. }
  477. // Truncate the buffer, cut off the bytes we processed
  478. if (buffer.position < buffer.length) {
  479. var ba:ByteArray = new ByteArray();
  480. ba.writeBytes(buffer, buffer.position, buffer.length - buffer.position);
  481. buffer = ba;
  482. } else {
  483. // The whole buffer has been processed
  484. buffer.length = 0;
  485. }
  486. }
  487. protected function parseBulk(len:int):ByteArray {
  488. // Process the bulk data body
  489. var ba:ByteArray = new ByteArray();
  490. // Copy [len] bytes
  491. if (len > 0) {
  492. buffer.readBytes(ba, 0, len);
  493. ba.position = 0;
  494. }
  495. // The data should be immediately followed by CR/LF
  496. var idx:int = findCRLF(buffer, buffer.position);
  497. if (idx >= 0) {
  498. if (idx > buffer.position) {
  499. // There's extra data, [len] bytes are not immediately followed by CR/LF
  500. trace("Warning: skipped " + (idx - buffer.position) + " bytes after bulk data");
  501. }
  502. // Skip to after CR/LF (start of next reply)
  503. buffer.position = idx + 2;
  504. }
  505. return ba;
  506. }
  507. protected function findCRLF(ba:ByteArray, startAtIndex:uint = 0):int {
  508. for (var i:uint = startAtIndex; i < ba.length - 1; i++) {
  509. if (ba[i] == 0x0d && ba[i + 1] == 0x0a) {
  510. return i;
  511. }
  512. }
  513. return -1;
  514. }
  515. }
  516. }