FlexとJavaScriptでMemcachedサーバに接続してみた

前回(memcachedサーバをPHPで作ってみた - yoshida_eth0の日記)はサーバ、今回はクライアント。
FlexMemcachedサーバにソケット通信する奴を作って、それをJavaScriptと連携させた。
果たして実用性があるかは解らないけど、技術的には出来た、という話。
作った後に調べたら、同じような事をしてる人がなんと3人もいた。
JNEXTもJaxerも使った事ない。
どうやらクライアントに各々インストールしなきゃいけないものらしい。
 
FLASH + JavaScriptmemcached と通信 - Hotdocs
http://www.hotdocs.jp/file/7139
 
Javascriptmemcachedにソケット通信する - akihitoの日記
http://d.hatena.ne.jp/t-akihito/20071005/p1
 
jsmemcached-client - Project Hosting on Google Code
http://code.google.com/p/jsmemcached-client/
 

対応コマンド

GZip圧縮は非対応。
・set
・add
・replace
・append
・prepend
・incr
・decr
・get
・delete
・flush_all
・quit
・version
 

作ったもの

jp/eth0/Memcached.as

ByteArrayでソケット通信するライブラリ。
mxフレームワーク使わなくても出来たけど、Flashで使う事なんてないだろうから、このままで。
 

jp/eth0/MemcachedStr.as

Stringで通信出来るようにしたMemcachedのラッパークラス。
オーバーロード出来ないから酷い有様になった。
 

MemcachedClient.mxml

MemcachedStrを使って作った。
機能一覧。
 

MemcachedJS.mxml

MemcachedStrを使って作った。
JavaScriptで使えるように、ExternalInterface.addCallback()とかExternalInterface.call()とかやってる。
JavaScript側からコールバック関数を指定するタイミングが難しかったからflashvarsで指定出来るようにした。
 

MemcachedJS.html

SWFと連携するためのJavaScript
 

ソース

jp/eth0/Memcached.as
package jp.eth0 {
	import flash.net.Socket;
	import flash.utils.ByteArray;
	import mx.utils.StringUtil;
	import flash.events.EventDispatcher;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.ProgressEvent;

	public class Memcached extends EventDispatcher {

		public static var RESPONSE:String = "response";

		private var sock:Socket;
		public var lastRequest:Object;
		public var lastResponse:Object;


		// コンストラクタ
		public function Memcached() {
			sock = new Socket()
			// イベントリスナー登録
			sock.addEventListener(Event.CLOSE, dispatchEvent);
			sock.addEventListener(Event.CONNECT, dispatchEvent);
			sock.addEventListener(IOErrorEvent.IO_ERROR, dispatchEvent);
			sock.addEventListener(SecurityErrorEvent.SECURITY_ERROR, dispatchEvent);
			sock.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
		}

		// 接続
		public function connect(host:String, port:int = 11211):void {
			sock.connect(host, port);
		}

		// set
		public function doSet(key:String, value:ByteArray, flag:int = 0, expire:int = 0, mode:String = 'set'):Boolean {
			var ba:ByteArray = new ByteArray();
			ba.writeUTFBytes(mode + " " + key + " " + flag + " " + expire + " " + value.length + "\r\n");
			ba.writeBytes(value);
			ba.writeUTFBytes("\r\n");
			return write(ba);
		}

		// add
		public function doAdd(key:String, value:ByteArray, flag:int = 0, expire:int = 0):Boolean {
			return doSet(key, value, flag, expire, 'add');
		}

		// replace
		public function doReplace(key:String, value:ByteArray, flag:int = 0, expire:int = 0):Boolean {
			return doSet(key, value, flag, expire, 'replace');
		}

		// append
		public function doAppend(key:String, value:ByteArray, flag:int = 0, expire:int = 0):Boolean {
			return doSet(key, value, flag, expire, 'append');
		}

		// prepend
		public function doPrepend(key:String, value:ByteArray, flag:int = 0, expire:int = 0):Boolean {
			return doSet(key, value, flag, expire, 'prepend');
		}

		// incr
		public function doIncr(key:String, value:int, mode:String = 'incr'):Boolean {
			var ba:ByteArray = new ByteArray();
			ba.writeUTFBytes(mode + " " + key + " " + value + "\r\n");
			return write(ba);
		}

		// decr
		public function doDecr(key:String, value:int):Boolean {
			return doIncr(key, value, 'decr');
		}

		// get
		public function doGet(key:String):Boolean {
			var ba:ByteArray = new ByteArray();
			ba.writeUTFBytes("get " + key + "\r\n");
			return write(ba);
		}

		// delete
		public function doDelete(key:String, timeout:int = 0):Boolean {
			var ba:ByteArray = new ByteArray();
			ba.writeUTFBytes("delete " + key + " " + timeout + "\r\n");
			return write(ba);
		}

		// flush
		public function doFlush(timeout:int = 0):Boolean {
			var ba:ByteArray = new ByteArray();
			ba.writeUTFBytes("flush_all " + timeout + "\r\n");
			return write(ba);
		}

		// quit
		public function doQuit():Boolean {
			var ba:ByteArray = new ByteArray();
			ba.writeUTFBytes("quit\r\n");
			return write(ba);
		}

		// version
		public function doVersion():Boolean {
			var ba:ByteArray = new ByteArray();
			ba.writeUTFBytes("version\r\n");
			return write(ba);
		}

		// ソケットに書き込む
		private function write(value:ByteArray):Boolean {
			lastRequest = new Object();
			if (isConnect()) {
				lastRequest['mode'] = StringUtil.trim(value.toString().split(' ')[0]);
				lastRequest['write'] = value;
				sock.writeBytes(value);
				sock.flush();
				return true;
			}
			return false;
		}


		/* イベント */

		// イベントハンドラ
		private function socketDataHandler(event:ProgressEvent):void {
			var response:ByteArray = new ByteArray();
			sock.readBytes(response);
			lastResponse = Memcached.parseResponse(lastRequest['mode'], response);
			dispatchEvent(new Event(Memcached.RESPONSE));
		}


		/* Util */

		// 接続中か否か
		public function isConnect():Boolean {
			if (sock && sock.connected) {
				return true;
			}
			return false;
		}


		/* レスポンス解析 */

		public static function parseResponse(mode:String, resp:ByteArray):Object {
			var result:Object = new Object();
			result['mode'] = mode;
			result['response'] = resp;
			result['success'] = false;
			switch (mode) {
			case 'set':
			case 'add':
			case 'replace':
			case 'append':
			case 'prepend':
				Memcached.parseSet(result);
				break;
			case 'incr':
			case 'decr':
				Memcached.parseIncr(result);
				break;
			case 'get':
				Memcached.parseGet(result);
				break;
			case 'delete':
				Memcached.parseDelete(result);
				break;
			case 'flush_all':
				Memcached.parseFlushAll(result);
				break;
			case 'quit':
				result['result'] = true;
				break;
			case 'version':
				Memcached.parseVersion(result);
				break;
			}
			return result;
		}

		private static function parseSet(result:Object):void {
			if (result['response'].toString()=="STORED\r\n") {
				result['success'] = true;
			}
		}

		private static function parseIncr(result:Object):void {
			if (result['response'].toString()!="NOT_FOUND\r\n") {
				result['success'] = true;
				result['value'] = int(StringUtil.trim(result['response']));
			}
		}

		private static function parseGet(result:Object):void {
			var info:String = result['response'].toString().split("\r\n", 1)[0];
			var info_arr:Array = info.split(" ");
			if (info_arr.length==4 && info_arr[0]=="VALUE") {
				result['success'] = true;
				result['key'] = info_arr[1];
				result['flag'] = int(info_arr[2]);
				result['length'] = int(info_arr[3]);
				result['value'] = new ByteArray();
				result['value'].writeBytes(result['response'], uint(info.length + 2), uint(info_arr[3]));
			}
		}

		private static function parseDelete(result:Object):void {
			if (result['response'].toString()=="DELETED\r\n") {
				result['success'] = true;
			}
		}

		private static function parseFlushAll(result:Object):void {
			if (result['response'].toString()=="OK\r\n") {
				result['success'] = true;
			}
		}

		private static function parseVersion(result:Object):void {
			if (result['response'].toString().substr(0, 8)=="VERSION ") {
				result['success'] = true;
				result['value'] = StringUtil.trim(result['response'].toString().substr(8));
			}
		}
	}
}

 

jp/eth0/MemcachedStr.as
package jp.eth0 {
	import flash.utils.ByteArray;
	import jp.eth0.Memcached;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.EventDispatcher;

	public class MemcachedStr extends EventDispatcher {

		public static var RESPONSE:String = "response";

		private var mc:Memcached;
		public var lastRequest:Object;
		public var lastResponse:Object;


		/* コンストラクタ */

		public function MemcachedStr() {
			mc = new Memcached();
			// イベントリスナー登録
			mc.addEventListener(Event.CLOSE, dispatchEvent);
			mc.addEventListener(Event.CONNECT, dispatchEvent);
			mc.addEventListener(IOErrorEvent.IO_ERROR, dispatchEvent);
			mc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, dispatchEvent);
			mc.addEventListener(Memcached.RESPONSE, responseHandler);
		}


		/* ラッパー */

		public function connect(host:String, port:int = 11211):void {
			mc.connect(host, port);
		}

		public function doSet(key:String, value:String, flag:int = 0, expire:int = 0):Boolean {
			var ba:ByteArray = new ByteArray();
			ba.writeUTFBytes(value);
			var result:Boolean = mc.doSet(key, ba, flag, expire);
			lastRequest = mc.lastRequest;
			return result;
		}

		public function doAdd(key:String, value:String, flag:int = 0, expire:int = 0):Boolean {
			var ba:ByteArray = new ByteArray();
			ba.writeUTFBytes(value);
			var result:Boolean = mc.doAdd(key, ba, flag, expire);
			lastRequest = mc.lastRequest;
			return result;
		}

		public function doReplace(key:String, value:String, flag:int = 0, expire:int = 0):Boolean {
			var ba:ByteArray = new ByteArray();
			ba.writeUTFBytes(value);
			var result:Boolean = mc.doReplace(key, ba, flag, expire);
			lastRequest = mc.lastRequest;
			return result;
		}

		public function doAppend(key:String, value:String, flag:int = 0, expire:int = 0):Boolean {
			var ba:ByteArray = new ByteArray();
			ba.writeUTFBytes(value);
			var result:Boolean = mc.doAppend(key, ba, flag, expire);
			lastRequest = mc.lastRequest;
			return result;
		}

		public function doPrepend(key:String, value:String, flag:int = 0, expire:int = 0):Boolean {
			var ba:ByteArray = new ByteArray();
			ba.writeUTFBytes(value);
			var result:Boolean = mc.doPrepend(key, ba, flag, expire);
			lastRequest = mc.lastRequest;
			return result;
		}

		public function doIncr(key:String, value:int):Boolean {
			var result:Boolean = mc.doIncr(key, value);
			lastRequest = mc.lastRequest;
			return result;
		}

		public function doDecr(key:String, value:int):Boolean {
			var result:Boolean = mc.doDecr(key, value);
			lastRequest = mc.lastRequest;
			return result;
		}

		public function doGet(key:String):Boolean {
			var result:Boolean = mc.doGet(key);
			lastRequest = mc.lastRequest;
			return result;
		}

		public function doDelete(key:String, timeout:int):Boolean {
			var result:Boolean = mc.doDelete(key, timeout);
			lastRequest = mc.lastRequest;
			return result;
		}

		public function doFlush(timeout:int):Boolean {
			var result:Boolean = mc.doFlush(timeout);
			lastRequest = mc.lastRequest;
			return result;
		}

		public function doQuit():Boolean {
			var result:Boolean = mc.doQuit();
			lastRequest = mc.lastRequest;
			return result;
		}

		public function doVersion():Boolean {
			var result:Boolean = mc.doVersion();
			lastRequest = mc.lastRequest;
			return result;
		}

		public function isConnect():Boolean {
			return mc.isConnect();
		}


		/* イベントハンドラ */

		public function responseHandler(event:Event):void {
			lastResponse = mc.lastResponse;
			for (var key:String in lastResponse) {
				if (mc.lastResponse[key] is ByteArray) {
					lastResponse[key] = lastResponse[key].toString();
				}
			}
			dispatchEvent(new Event(MemcachedStr.RESPONSE));
		}
	}
}

 

MemcachedClient.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" applicationComplete="init()">
	<mx:Script>
		<![CDATA[
			import flash.events.*;
			import mx.controls.Alert;
			import mx.utils.ObjectUtil;
			import jp.eth0.MemcachedStr;


			/* var */

			[Bindable] private var mc:MemcachedStr;


			/* init */

			private function init():void{
				mc = new MemcachedStr();
				mc.addEventListener(Event.CLOSE, closeHandler);
				mc.addEventListener(Event.CONNECT, connectHandler);
				mc.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
				mc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
				mc.addEventListener(MemcachedStr.RESPONSE, responseHandler);
			}


			/* イベントハンドラ */

			public function closeHandler(event:Event):void {
				Alert.show("close");
			}

			public function connectHandler(event:Event):void {
				Alert.show("connect");
			}

			public function ioErrorHandler(event:IOErrorEvent):void {
				Alert.show("io_error");
			}

			public function securityErrorHandler(event:SecurityErrorEvent):void {
				Alert.show("security_error");
			}

			public function responseHandler(event:Event):void {
				Alert.show(ObjectUtil.toString(mc.lastResponse));
			}
		]]>
	</mx:Script>


	<!-- connect -->
	<mx:Form>
		<mx:FormHeading label="connect"/>
		<mx:FormItem label="host">
			<mx:TextInput id="host" width="200" text="localhost"/>
		</mx:FormItem>
		<mx:FormItem label="port">
			<mx:TextInput id="port" width="200" text="11211"/>
		</mx:FormItem>
		<mx:FormItem>
			<mx:Button label="submit" click="{mc.connect(host.text, int(port.text))}"/>
		</mx:FormItem>
	</mx:Form>

	<!-- set -->
	<mx:Form>
		<mx:FormHeading label="set"/>
		<mx:FormItem label="key">
			<mx:TextInput id="set_key" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="value">
			<mx:TextInput id="set_value" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="flag">
			<mx:TextInput id="set_flag" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="expire">
			<mx:TextInput id="set_expire" width="200"/>
		</mx:FormItem>
		<mx:FormItem>
			<mx:Button label="submit" click="{mc.doSet(set_key.text, set_value.text, int(set_flag.text), int(set_expire.text))}"/>
		</mx:FormItem>
	</mx:Form>

	<!-- add -->
	<mx:Form>
		<mx:FormHeading label="add"/>
		<mx:FormItem label="key">
			<mx:TextInput id="add_key" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="value">
			<mx:TextInput id="add_value" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="flag">
			<mx:TextInput id="add_flag" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="expire">
			<mx:TextInput id="add_expire" width="200"/>
		</mx:FormItem>
		<mx:FormItem>
			<mx:Button label="submit" click="{mc.doAdd(add_key.text, add_value.text, int(add_flag.text), int(add_expire.text))}"/>
		</mx:FormItem>
	</mx:Form>

	<!-- replace -->
	<mx:Form>
		<mx:FormHeading label="replace"/>
		<mx:FormItem label="key">
			<mx:TextInput id="replace_key" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="value">
			<mx:TextInput id="replace_value" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="flag">
			<mx:TextInput id="replace_flag" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="expire">
			<mx:TextInput id="replace_expire" width="200"/>
		</mx:FormItem>
		<mx:FormItem>
			<mx:Button label="submit" click="{mc.doReplace(replace_key.text, replace_value.text, int(replace_flag.text), int(replace_expire.text))}"/>
		</mx:FormItem>
	</mx:Form>

	<!-- append -->
	<mx:Form>
		<mx:FormHeading label="append"/>
		<mx:FormItem label="key">
			<mx:TextInput id="append_key" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="value">
			<mx:TextInput id="append_value" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="flag">
			<mx:TextInput id="append_flag" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="expire">
			<mx:TextInput id="append_expire" width="200"/>
		</mx:FormItem>
		<mx:FormItem>
			<mx:Button label="submit" click="{mc.doAppend(append_key.text, append_value.text, int(append_flag.text), int(append_expire.text))}"/>
		</mx:FormItem>
	</mx:Form>

	<!-- prepend -->
	<mx:Form>
		<mx:FormHeading label="prepend"/>
		<mx:FormItem label="key">
			<mx:TextInput id="prepend_key" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="value">
			<mx:TextInput id="prepend_value" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="flag">
			<mx:TextInput id="prepend_flag" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="expire">
			<mx:TextInput id="prepend_expire" width="200"/>
		</mx:FormItem>
		<mx:FormItem>
			<mx:Button label="submit" click="{mc.doPrepend(prepend_key.text, prepend_value.text, int(prepend_flag.text), int(prepend_expire.text))}"/>
		</mx:FormItem>
	</mx:Form>

	<!-- incr -->
	<mx:Form>
		<mx:FormHeading label="incr"/>
		<mx:FormItem label="key">
			<mx:TextInput id="incr_key" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="value">
			<mx:TextInput id="incr_value" width="200"/>
		</mx:FormItem>
		<mx:FormItem>
			<mx:Button label="submit" click="{mc.doIncr(incr_key.text, int(incr_value.text))}"/>
		</mx:FormItem>
	</mx:Form>

	<!-- decr -->
	<mx:Form>
		<mx:FormHeading label="decr"/>
		<mx:FormItem label="key">
			<mx:TextInput id="decr_key" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="value">
			<mx:TextInput id="decr_value" width="200"/>
		</mx:FormItem>
		<mx:FormItem>
			<mx:Button label="submit" click="{mc.doDecr(decr_key.text, int(decr_value.text))}"/>
		</mx:FormItem>
	</mx:Form>

	<!-- get -->
	<mx:Form>
		<mx:FormHeading label="get"/>
		<mx:FormItem label="key">
			<mx:TextInput id="get_key" width="200"/>
		</mx:FormItem>
		<mx:FormItem>
			<mx:Button label="submit" click="{mc.doGet(get_key.text)}"/>
		</mx:FormItem>
	</mx:Form>

	<!-- delete -->
	<mx:Form>
		<mx:FormHeading label="delete"/>
		<mx:FormItem label="key">
			<mx:TextInput id="delete_key" width="200"/>
		</mx:FormItem>
		<mx:FormItem label="timeout">
			<mx:TextInput id="delete_timeout" width="200"/>
		</mx:FormItem>
		<mx:FormItem>
			<mx:Button label="submit" click="{mc.doDelete(delete_key.text, int(delete_timeout.text))}"/>
		</mx:FormItem>
	</mx:Form>

	<!-- flush -->
	<mx:Form>
		<mx:FormHeading label="flush"/>
		<mx:FormItem label="timeout">
			<mx:TextInput id="flush_timeout" width="200"/>
		</mx:FormItem>
		<mx:FormItem>
			<mx:Button label="submit" click="{mc.doFlush(int(flush_timeout.text))}"/>
		</mx:FormItem>
	</mx:Form>

	<!-- quit -->
	<mx:Form>
		<mx:FormHeading label="quit"/>
		<mx:FormItem>
			<mx:Button label="submit" click="{mc.doQuit()}"/>
		</mx:FormItem>
	</mx:Form>

	<!-- version -->
	<mx:Form>
		<mx:FormHeading label="version"/>
		<mx:FormItem>
			<mx:Button label="submit" click="{mc.doVersion()}"/>
		</mx:FormItem>
	</mx:Form>

	<!-- isConnect -->
	<mx:Form>
		<mx:FormHeading label="isConnect"/>
		<mx:FormItem>
			<mx:Button label="submit" click="{Alert.show(mc.isConnect().toString())}"/>
		</mx:FormItem>
	</mx:Form>

</mx:Application>

 

MemcachedJS.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" applicationComplete="init()">
	<mx:Script>
		<![CDATA[
			import flash.events.*;
			import mx.controls.Alert;
			import mx.utils.ObjectUtil;
			import jp.eth0.MemcachedStr;
			import flash.system.Security;


			/* var */

			private var mc:MemcachedStr;
			private var handler:Object = {
				'create':null,
				'close':null,
				'connect':null,
				'io_error':null,
				'security_error':null,
				'response':null
			};


			/* init */

			private function init():void{
				mc = new MemcachedStr();
				// JavaScriptからのアクセスを許可
				Security.allowDomain('*');
				// JavaScriptから呼び出す関数を登録
				ExternalInterface.addCallback('setHandler', setHandler);
				ExternalInterface.addCallback('connect', mc.connect);
				ExternalInterface.addCallback('doSet', mc.doSet);
				ExternalInterface.addCallback('doAdd', mc.doAdd);
				ExternalInterface.addCallback('doReplace', mc.doReplace);
				ExternalInterface.addCallback('doAppend', mc.doAppend);
				ExternalInterface.addCallback('doPrepend', mc.doPrepend);
				ExternalInterface.addCallback('doIncr', mc.doIncr);
				ExternalInterface.addCallback('doDecr', mc.doDecr);
				ExternalInterface.addCallback('doGet', mc.doGet);
				ExternalInterface.addCallback('doDelete', mc.doDelete);
				ExternalInterface.addCallback('doFlush', mc.doFlush);
				ExternalInterface.addCallback('doQuit', mc.doQuit);
				ExternalInterface.addCallback('doVersion', mc.doVersion);
				ExternalInterface.addCallback('isConnect', mc.isConnect);
				// イベントリスナー登録
				mc.addEventListener(Event.CLOSE, closeHandler);
				mc.addEventListener(Event.CONNECT, connectHandler);
				mc.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
				mc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
				mc.addEventListener(MemcachedStr.RESPONSE, responseHandler);
				// FlashVarsからハンドラ登録
				for (var event:String in handler) {
					if (Application.application.parameters[event]!=null) {
						handler[event] = Application.application.parameters[event];
					}
				}
				// Createハンドラが設定されていたら実行
				if (handler['create']) {
					ExternalInterface.call(handler['create']);
				}
			}


			/* イベントリスナー設定 */

			public function setHandler(event:String, jsfunc:String):Boolean {
				Alert.show("setHandler "+event+" "+jsfunc);
				if (handler.hasOwnProperty(event)) {
					handler[event] = jsfunc;
					return true;
				}
				return false;
			}


			/* イベントハンドラ */

			public function closeHandler(event:Event):void {
				Alert.show("close");
				if (handler['close']) {
					ExternalInterface.call(handler['close']);
				}
			}

			public function connectHandler(event:Event):void {
				Alert.show("connect");
				if (handler['connect']) {
					ExternalInterface.call(handler['connect']);
				}
			}

			public function ioErrorHandler(event:IOErrorEvent):void {
				Alert.show("io_error");
				if (handler['io_error']) {
					ExternalInterface.call(handler['io_error']);
				}
			}

			public function securityErrorHandler(event:SecurityErrorEvent):void {
				Alert.show("security_error");
				if (handler['security_error']) {
					ExternalInterface.call(handler['security_error']);
				}
			}

			public function responseHandler(event:Event):void {
				Alert.show(ObjectUtil.toString(mc.lastResponse));
				if (handler['response']) {
					ExternalInterface.call(handler['response'], mc.lastResponse);
				}
			}
		]]>
	</mx:Script>
</mx:Application>

 

MemcachedJS.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ja" xml:lang="ja">
<head>
<title>Memcached Javascript</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript">
	swfobject.embedSWF("MemcachedJS.swf", "MemcachedJS", "300", "150", "9.0.0", null, {'create':'setHandler'}, null, {'allowScriptAccess':'always'});
	function thisMovie(movieName) {
		if (navigator.appName.indexOf("Microsoft") != -1) {
			return window[movieName];
		}
		else {
			return document[movieName];
		}
	}
	function setHandler() {
		thisMovie('MemcachedJS').setHandler('close', 'closeHandler');
		thisMovie('MemcachedJS').setHandler('connect', 'connectHandler');
		thisMovie('MemcachedJS').setHandler('io_error', 'ioErrorHandler');
		thisMovie('MemcachedJS').setHandler('security_error', 'securityErrorHandler');
		thisMovie('MemcachedJS').setHandler('response', 'responseHandler');
	}
	function closeHandler() {
		showLog("close");
	}
	function connectHandler() {
		showLog("connect");
	}
	function ioErrorHandler() {
		showLog("io_error");
	}
	function securityErrorHandler() {
		showLog("security_error");
	}
	function responseHandler(resp) {
		showLog(resp)
	}
	function showLog(log) {
		if ("console" in window) {
			console.log(log);
		} else {
			var message = '';
			if (typeof(log)=='object') {
				for (var i in log) {
					if (typeof(log[i])=='string') {
						message += i + ' = "' + log[i] + '"' + "\n";
					} else {
						message += i + ' = ' + log[i] + "\n";
					}
				}
			} else {
				message = log;
			}
			alert(message);
		}
	}
</script>
</head>

<body>
	<div id="MemcachedJS">
		<h1>Alternative content</h1>
		<p><a href="http://www.adobe.com/go/getflashplayer"><img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" /></a></p>
	</div>
	<hr />

	<!-- connect -->
	<form>
		connect<br />
		host : <input type="text" id="host" value="localhost" /><br />
		port : <input type="text" id="port" value="11211" /><br />
		<input type="button" value="submit" onclick="thisMovie('MemcachedJS').connect(host.value, parseInt(port.value));">
	</form>
	<hr />

	<!-- set -->
	<form>
		set<br />
		key : <input type="text" id="set_key" value="" /><br />
		value : <input type="text" id="set_value" value="" /><br />
		flag : <input type="text" id="set_flag" value="0" /><br />
		exipire : <input type="text" id="set_expire" value="0" /><br />
		<input type="button" value="submit" onclick="thisMovie('MemcachedJS').doSet(set_key.value, set_value.value, parseInt(set_flag.value), parseInt(set_expire.value));">
	</form>
	<hr />

	<!-- add -->
	<form>
		add<br />
		key : <input type="text" id="add_key" value="" /><br />
		value : <input type="text" id="add_value" value="" /><br />
		flag : <input type="text" id="add_flag" value="0" /><br />
		exipire : <input type="text" id="add_expire" value="0" /><br />
		<input type="button" value="submit" onclick="thisMovie('MemcachedJS').doAdd(add_key.value, add_value.value, parseInt(add_flag.value), parseInt(add_expire.value));">
	</form>
	<hr />

	<!-- replace -->
	<form>
		replace<br />
		key : <input type="text" id="replace_key" value="" /><br />
		value : <input type="text" id="replace_value" value="" /><br />
		flag : <input type="text" id="replace_flag" value="0" /><br />
		exipire : <input type="text" id="replace_expire" value="0" /><br />
		<input type="button" value="submit" onclick="thisMovie('MemcachedJS').doReplace(replace_key.value, replace_value.value, parseInt(replace_flag.value), parseInt(replace_expire.value));">
	</form>
	<hr />

	<!-- append -->
	<form>
		append<br />
		key : <input type="text" id="append_key" value="" /><br />
		value : <input type="text" id="append_value" value="" /><br />
		flag : <input type="text" id="append_flag" value="0" /><br />
		exipire : <input type="text" id="append_expire" value="0" /><br />
		<input type="button" value="submit" onclick="thisMovie('MemcachedJS').doAppend(append_key.value, append_value.value, parseInt(append_flag.value), parseInt(append_expire.value));">
	</form>
	<hr />

	<!-- prepend -->
	<form>
		prepend<br />
		key : <input type="text" id="prepend_key" value="" /><br />
		value : <input type="text" id="prepend_value" value="" /><br />
		flag : <input type="text" id="prepend_flag" value="0" /><br />
		exipire : <input type="text" id="prepend_expire" value="0" /><br />
		<input type="button" value="submit" onclick="thisMovie('MemcachedJS').doPrepend(prepend_key.value, prepend_value.value, parseInt(prepend_flag.value), parseInt(prepend_expire.value));">
	</form>
	<hr />

	<!-- incr -->
	<form>
		incr<br />
		key : <input type="text" id="incr_key" value="" /><br />
		value : <input type="text" id="incr_value" value="" /><br />
		<input type="button" value="submit" onclick="thisMovie('MemcachedJS').doIncr(incr_key.value, parseInt(incr_value.value));">
	</form>
	<hr />

	<!-- decr -->
	<form>
		decr<br />
		key : <input type="text" id="decr_key" value="" /><br />
		value : <input type="text" id="decr_value" value="" /><br />
		<input type="button" value="submit" onclick="thisMovie('MemcachedJS').doDecr(decr_key.value, parseInt(decr_value.value));">
	</form>
	<hr />

	<!-- get -->
	<form>
		get<br />
		key : <input type="text" id="get_key" value="" /><br />
		<input type="button" value="submit" onclick="thisMovie('MemcachedJS').doGet(get_key.value);">
	</form>
	<hr />

	<!-- delete -->
	<form>
		delete<br />
		key : <input type="text" id="delete_key" value="" /><br />
		timeout : <input type="text" id="delete_timeout" value="" /><br />
		<input type="button" value="submit" onclick="thisMovie('MemcachedJS').doDelete(delete_key.value, parseInt(delete_timeout.value));">
	</form>
	<hr />

	<!-- flush -->
	<form>
		flush<br />
		timeout : <input type="text" id="flush_timeout" value="" /><br />
		<input type="button" value="submit" onclick="thisMovie('MemcachedJS').doFlush(parseInt(flush_timeout.value));">
	</form>
	<hr />

	<!-- quit -->
	<form>
		quit<br />
		<input type="button" value="submit" onclick="thisMovie('MemcachedJS').doQuit();">
	</form>
	<hr />

	<!-- version -->
	<form>
		version<br />
		<input type="button" value="submit" onclick="thisMovie('MemcachedJS').doVersion();">
	</form>
	<hr />

	<!-- isConnect -->
	<form>
		isConnect<br />
		<input type="button" value="submit" onclick="showLog(thisMovie('MemcachedJS').isConnect());">
	</form>

</body>
</html>

 

スクリーンショット

MemcachedClient


 

MemcachedJS (InternetExplorer)


 

MemcachedJS (Firefox)


 

追記 (2009-12-31 04:33:47)

1つ目のリンクの、Flash+JavaScriptMemcachedに通信のプレゼンしてる方のはてな記事みつけた。
タイトルがアレだから見つからなかったっぽい。
二番煎じの存在アピールにトラックバック送信させて頂いたり頂かなかったり。
ニコニコ大百科とか作ってる方らしい。
やっぱりはてなはすごい人いっぱいいるなぁ。
もっと早く始めればよかった。
 
グニャラは大変なFlashを描いていきました - グニャラくんのグニャグニャ備忘録@はてな
http://d.hatena.ne.jp/tasukuchan/20080127/flash_javascript_perl
 
グニャラくん ★とは (グニャラクンとは) - ニコニコ大百科
http://dic.nicovideo.jp/a/%E3%82%B0%E3%83%8B%E3%83%A3%E3%83%A9%E3%81%8F%E3%82%93%20%E2%98%85