ReverseHTTPを使ってみる
2ヶ月ぶりだろうか、エントリーを追加するのは。
その間、本業の方が洒落にならないくらい忙しく、趣味プログラミングは殆どできなかった。
(サンプルコードを見れば想像できると思うけど)本業はプログラマー/SEではないので、
開発環境には殆ど触れない生活ばかり。
そんな中お盆休みに入り、多少時間にゆとりができたので、
ドラクエ9に没頭する相方を尻目に、なにやらちょっとした騒ぎがあるReverseHTTPをテーマに自宅で少しプログラムをしてみた。
以前のエントリーでぶち上げたウィジェットマネージャは、αレベルくらいなものは出来上がってるんだけど、
思いついたら機能を追加、という形だったので、設計がひど過ぎて、脳内再設計に入りかけたところで、
本業多忙になってしまい、殆ど手付かずなんだけどねw
で、ReverseHTTP。
気分転換にイロイロWebを見てると、こういうところで知ったのがきっかけ。
- Big Sky :: ReverseHttpで誰よりも速く「はてなブックマーク」に反応するツール書いた。
- 「ReverseHTTPで誰よりも速く...」をブラウザだけで実現してみた。 - 今日もスミマセン。
『Javascriptでできるんだから、AIRでもできるべ』と安直に考え、ザックリ実装したのがこちら。
バグも少々<(_ _)>
HTTPDeamon.as
package net { import flash.events.Event; import flash.events.EventDispatcher; import flash.events.HTTPStatusEvent; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.net.URLRequest; import flash.net.URLRequestMethod; import flash.net.URLVariables; import flash.system.System; import flash.utils.ByteArray; import flash.utils.Dictionary; import mx.core.IMXMLObject; import events.HTTPDeamonEvent; [Event(name="listenComplete", type = "org.castor.events.HTTPDeamonEvent")] [Event(name="stopComplete", type = "org.castor.events.HTTPDeamonEvent")] [Event(name="receiveRequest", type = "org.castor.events.HTTPDeamonEvent")] [Event(name="listenError", type = "org.castor.events.HTTPDeamonEvent")] public class HTTPDeamon extends EventDispatcher implements IMXMLObject{ // Protected Variables protected var _request:URLRequest = null; protected var _loader:URLLoader = null; protected var _nextUrl:String = ""; protected var _responseMap:Dictionary = new Dictionary(); protected var _requestMap:Dictionary = new Dictionary(); protected var _endpoint:String = "http://www.reversehttp.net/reversehttp"; protected var _serverType:String = "AIRHTTPD"; protected var _token:String = "-"; protected var _label:String = ""; protected var _isRunning:Boolean = false; protected var _responseStatusMap:Object = { 100:"Continue", 101:"Switching Protocols", 102:"Processing", 200:"OK", 201:"Created", 202:"Accepted", 203:"Non-Authoritative Information", 204:"No Content", 205:"Reset Content", 206:"Partial Content", 207:"Multi-Status", 226:"IM Used", 300:"Multiple Choices", 301:"Moved Permanently", 302:"Found", 303:"See Other", 304:"Not Modified", 305:"Use Proxy", 306:"(Unused)", 307:"Temporary Redirect", 350:"", 400:"Bad Request", 401:"Unauthorized", 402:"Payment Required", 403:"Forbidden", 404:"Not Found", 405:"Method Not Allowed", 406:"Not Acceptable", 407:"Proxy Authentication Required", 408:"Request Timeout", 409:"Conflict", 410:"Gone", 411:"Length Required", 412:"Precondition Failed", 413:"Request Entity Too Large", 414:"Request-URI Too Long", 415:"Unsupported Media Type", 416:"Requested Range Not Satisfiable", 417:"Expectation Failed", 418:"", 420:"", 421:"", 422:"Unprocessable Entity", 423:"Locked", 424:"Failed Dependency", 425:"", 426:"Upgrade Required", 500:"Internal Server Error", 501:"Not Implemented", 502:"Bad Gateway", 503:"Service Unavailable", 504:"Gateway Timeout", 505:"HTTP Version Not Supported", 506:"Variant Also Negotiates", 507:"Insufficient Storage", 510:"Not Extended" }; // Constructor public function HTTPDeamon() { _request = new URLRequest(); } // Public Methods public function listen():void { _nextUrl = ""; serve(true); } public function respond(httpDeamonEvent:HTTPDeamonEvent, responseState:int, contentType:String, responseHeaders:Array, responseBody:String):void { var url:String = _requestMap[httpDeamonEvent].toString(); var responseStateString:String = _responseStatusMap[responseState].toString(); var responseURLRequest:URLRequest; var responseURLLoader:URLLoader; var responseData:Array = []; if(url != null && url != "" && responseStateString != null && responseStateString != "") { responseURLRequest = new URLRequest(); responseURLLoader = new URLLoader(); responseURLRequest.method = URLRequestMethod.POST; responseData.push("HTTP/1.1 " + responseState.toString() + " " + responseStateString); responseData.push("Server: " + _serverType); for each(var header:Object in responseHeaders) { if(header.name != null && header.value != null) { responseData.push(header.name.toString() + ": " + header.value.toString()); } } responseData.push("Content-Type: " + contentType); responseData.push("Content-Length: " + responseBody.length); responseData.push(""); responseURLRequest.data = responseData.join("\r\n") + "\r\n" + responseBody; responseURLRequest.contentType = "message/http"; responseURLRequest.url = url; responseURLLoader.load(responseURLRequest); delete _requestMap[httpDeamonEvent]; } } public function stop():void { _loader.close(); _isRunning = false; } // Public Overrided Methods public function initialized(document:Object, id:String):void { } // Public Getters/Setters public function get isRunning():Boolean { return _isRunning; } public function get label():String { return _label; } public function set label(value:String):void { _label = value; } public function get token():String { return _token; } public function set token(value:String):void { _token = value; } public function get endpoint():String { return _endpoint; } public function set endpoint(value:String):void { _endpoint = value; } public function get serverType():String { return _serverType; } public function set serverType(value:String):void { _serverType = value; } // Protected Methods protected function serve(newLoader:Boolean=false):void { var variables:URLVariables = null; if(newLoader) { _loader = new URLLoader(); _loader.addEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS, loaderHTTPResponseStatusHandler); _loader.addEventListener(Event.COMPLETE, loaderCompleteHandler); _loader.addEventListener(IOErrorEvent.IO_ERROR, loaderIOErrorHandler); _loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loaderSecurityErrorHandler); _loader.dataFormat = URLLoaderDataFormat.BINARY; } if(_nextUrl == "") { variables = new URLVariables(); variables.name = _label; variables.token = _token; _request.data = variables; _request.url = _endpoint; _request.method = URLRequestMethod.POST; } else { _request.url = _nextUrl; _request.method = URLRequestMethod.GET; } _loader.load(_request); System.gc(); } // Protected Overrided Methods // Protected Event Handlers protected function loaderHTTPResponseStatusHandler(event:HTTPStatusEvent):void { trace("loaderHTTPResponseStatusHandler"); var header:Object; var headerName:String; var headerValue:String; _nextUrl = ""; for each(header in event.responseHeaders) { headerName = header.name; headerValue = header.value; if(headerName == "Link") { _nextUrl = headerValue.substring(headerValue.indexOf("<")+1, headerValue.indexOf(">;")); } } var httpDeamonEvent:HTTPDeamonEvent; if(_nextUrl != "") { _responseMap[event.currentTarget] = { status: event.status, url: event.responseURL, headers: event.responseHeaders }; } else { httpDeamonEvent = new HTTPDeamonEvent(HTTPDeamonEvent.LISTEN_ERROR); this.dispatchEvent(httpDeamonEvent); } } protected function loaderCompleteHandler(event:Event):void { var loader:URLLoader = event.currentTarget as URLLoader; var response:Object = _responseMap[loader]; if(response == null) return; if(response.url == _endpoint) { // listen()直後のResponse httpDeamonEvent = new HTTPDeamonEvent(HTTPDeamonEvent.LISTEN_COMPLETE); _isRunning = true; this.dispatchEvent(httpDeamonEvent); serve(false); } else if(response.status == 204) { // serve()がTimeout serve(false); } else if(response.status == 200) { // serve()後にRequestアリ serve(true); var requestData:ByteArray = loader.data; var dataAsStr:String = requestData.toString(); var body:ByteArray = new ByteArray(); var httpSeparator:String = "\r\n\r\n"; var sepPos:int = dataAsStr.indexOf(httpSeparator); requestData.position = sepPos + httpSeparator.length; requestData.readBytes(body, 0); body.position = 0; var headers:Array = dataAsStr.substring(0,sepPos).split("\r\n"); var headerFirstLine:String = headers.shift().toString(); var methodLength:int = headerFirstLine.indexOf(" "); var method:String = headerFirstLine.substring(0, methodLength); var versionPos:int = headerFirstLine.lastIndexOf("HTTP"); var path:String = headerFirstLine.substring(methodLength+1, versionPos-1); var httpVersion:String = headerFirstLine.substring(versionPos); var headerLine:String; for (var i:int=0;i<headers.length;i++) { headerLine = headers[i]; if(headerLine == "") { headers.splice(i, headers.length-i); break; } } var request:HTTPRequestObject = new HTTPRequestObject(method, path, httpVersion, headers, body); var httpDeamonEvent:HTTPDeamonEvent; httpDeamonEvent = new HTTPDeamonEvent(HTTPDeamonEvent.RECEIVE_REQUEST, request); _requestMap[httpDeamonEvent] = _responseMap[loader].url; delete _responseMap[loader]; this.dispatchEvent(httpDeamonEvent); } else { httpDeamonEvent = new HTTPDeamonEvent(HTTPDeamonEvent.LISTEN_ERROR); this.dispatchEvent(httpDeamonEvent); } } protected function loaderIOErrorHandler(event:IOErrorEvent):void { trace("loaderIOErrorHandler"); } protected function loaderSecurityErrorHandler(event:SecurityErrorEvent):void { trace("loaderSecurityErrorHandler"); } } }
HTTPRequestObject.as
package net { import flash.utils.ByteArray; import mx.utils.ObjectUtil; public class HTTPRequestObject { protected var _method:String = ""; protected var _path:String = ""; protected var _httpVersion:String = ""; protected var _headers:Array = []; protected var _body:ByteArray = null; public function HTTPRequestObject(method:String, path:String="/", httpVersion:String="HTTP/1.1", headers:Array=null, body:ByteArray=null) { _method = method; _path = path; _httpVersion = httpVersion; _headers = headers; _body = ObjectUtil.copy(body) as ByteArray; _body.position = 0; } public function get requestMethod():String { return _method; } public function get requestPath():String { return _path; } public function get requestHttpVersion():String { return _httpVersion; } public function get requestHeaders():Array { return _headers; } public function get requestBody():ByteArray { return _body; } } }
HTTPDeamonEvent.as
package events { import flash.events.Event; import net.HTTPRequestObject; public class HTTPDeamonEvent extends Event { public static const LISTEN_COMPLETE:String = "listenComplete"; public static const LISTEN_ERROR:String = "listenError"; public static const STOP_COMPLETE:String = "stopComplete"; public static const RECEIVE_REQUEST:String = "receiveRequest"; protected var _request:HTTPRequestObject = null; public function HTTPDeamonEvent(type:String, request:HTTPRequestObject=null, bubbles:Boolean=false, cancelable:Boolean=false) { super(type, bubbles, cancelable); _request = request; } public function get request():HTTPRequestObject { return _request; } } }
簡単な使い方。このアプリケーションを起動したあとに、
ブラウザ等でhttp://hogefuga.www.reversehttp.net/にアクセスすると、リクエストヘッダを返してくれる。
ただそれだけ。
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:net="net.*" creationComplete="onCreationCompleteHandler(event)"> <mx:Script> <![CDATA[ import events.HTTPDeamonEvent; import mx.events.FlexEvent; protected function onCreationCompleteHandler(event:FlexEvent):void { httpd.addEventListener(HTTPDeamonEvent.LISTEN_COMPLETE, httpdListenCompleteHandler); httpd.addEventListener(HTTPDeamonEvent.LISTEN_ERROR, httpdListenErrorHandler); httpd.addEventListener(HTTPDeamonEvent.STOP_COMPLETE, httpdStopCompleteHandler); httpd.addEventListener(HTTPDeamonEvent.RECEIVE_REQUEST, httpdReceiveRequestHandler); httpd.listen(); } protected function httpdListenCompleteHandler(event:HTTPDeamonEvent):void { trace("httpdListenCompleteHandler"); btn.enabled = true; } protected function httpdReceiveRequestHandler(event:HTTPDeamonEvent):void { trace("httpdReceiveRequestHandler"); var body:Array = []; body.push(event.request.requestMethod + " " + event.request.requestPath + " " +event.request.requestHttpVersion); for each(var header:String in event.request.requestHeaders) { body.push(header); } httpd.respond(event, 200, "text/plain", [], "\r\n" + body.join("\r\n")); trace(event.request.requestBody.readBoolean()); } protected function httpdStopCompleteHandler(event:HTTPDeamonEvent):void { trace("httpdStopCompleteHandler"); } protected function httpdListenErrorHandler(event:HTTPDeamonEvent):void { trace("httpdListenErrorHandler"); } ]]> </mx:Script> <net:HTTPDeamon id="httpd" label="hogefuga"/> </mx:WindowedApplication>
ReverseHTTP、考え方としては面白いけど、
いろんなサービスがWebHookに対応してくれないと、使い方に困るかもなぁ。
ブラウザにドラッグ&ドロップ
ちょっと前にはてブしたんだが。
Google Waveが発表されて、そのデモの中でブラウザにファイルをドラッグ&ドロップしてたとか。
そのやり方を解説してる方がいたので、そのままやってみた。
確かにできた。スゴイ!
で、javascriptでできるんだからFlashでもできるよね、と安直な発想でやってみた。
DnDSample.mxml
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" horizontalAlign="center" verticalAlign="middle" creationComplete="logic.onCreationCompleteHandler(event)" backgroundColor="0xffffff" backgroundGradientAlphas="[0,0]" xmlns:local="*"> <local:DnDSampleLogic id="logic"/> <mx:Label id="lbl" text="" fontSize="48" /> </mx:Application>
まぁ、メイン部分はたいしたことやってない。
アプリケーションのメインロジック。GearsのjavascriptとFlexを繋げてる。
DnDSampleLogic.as
package { import flash.events.MouseEvent; import flash.external.ExternalInterface; import flash.net.URLRequest; import flash.net.navigateToURL; import mx.core.IMXMLObject; import mx.events.FlexEvent; import utils.JSUtil; public class DnDSampleLogic implements IMXMLObject { protected var view:DnDSample; public function DnDSampleLogic() { } public function initialized(document:Object, id:String):void { view = document as DnDSample; } public function onCreationCompleteHandler(event:FlexEvent):void { // gears_init.jsの内容をそのまま書き込む(こういう使い方して問題ないのか?) JSUtil.writeJSFunction( "gears_init" ,[] , "if (window.google && google.gears) {" + "return;" + "}" + "var factory = null;" + "/* Firefox */" + "if (typeof GearsFactory != 'undefined') {" + "factory = new GearsFactory();" + "} else {" + "/* IE */" + "try {" + "factory = new ActiveXObject('Gears.Factory');" + "/* privateSetGlobalObject is only required and supported on IE Mobile on */" + "/* WinCE. */" + "if (factory.getBuildInfo().indexOf('ie_mobile') != -1) {" + "factory.privateSetGlobalObject(this);" + "}" + "} catch (e) {" + "/* Safari */" + "if ((typeof navigator.mimeTypes != 'undefined')" + "&& navigator.mimeTypes['application/x-googlegears']) {" + "factory = document.createElement('object');" + "factory.style.display = 'none';" + "factory.width = 0;" + "factory.height = 0;" + "factory.type = 'application/x-googlegears';" + "document.documentElement.appendChild(factory);" + "}" + "}" + "}" + "if (!factory) {" + "return false;" + "}" + "if (!window.google) {" + "google = {};" + "}" + "if (!google.gears) {" + "google.gears = {factory: factory};" + "}" + "return true;" ); // 書き込んだgears_init.jsをおもむろに実行 ExternalInterface.call("gears_init"); // ドロップ時に呼ばれるコールバック関数を登録 ExternalInterface.addCallback( "sendData", function(obj:Object):void{ // コールバック関数 trace(obj); } ); // 以下、リンク先のサンプルのjavascriptを書き込む JSUtil.writeJSFunction( "addEvent" , ["element", "name", "handler"] , "if(platform != 'IE') {" + "element.addEventListener(" + "platform != 'Firefox' ? name : eventMap[name]," + "handler," + "false" + ");" + "} else {" + "element.attachEvent('on' + name, handler);" + "}" ); JSUtil.writeJSFunction( "escapeHtml" , ["s"] , "return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');" ); JSUtil.writeJSFunction( "handleEnterOver" , ["event"] , "event.returnValue = false;" + "desktop.setDragCursor(event, 'copy');" ); JSUtil.writeJSFunction( "handleLeave" , ["event"] , "event.returnValue = false;" + "desktop.setDragCursor(event, 'none');" ); JSUtil.writeJSFunction( "handleDrop" , ["event"] , "event.stopPropagation && event.stopPropagation();" + "targetSWF.sendData(desktop.getDragData(event, 'application/x-gears-files'));" ); JSUtil.writeJSFunction( "dnd_init" , [] , "desktop = null;" + "action = ['install', 'インストール'];" + "platform = null;" + "targetSWF = null;" + "var initialized = false;" + "try {" + "if(!window.google || !window.google.gears) throw null;" + "action = ['upgrade', '更新'];" + "desktop = google.gears.factory.create('beta.desktop');" + "var buildInfo = google.gears.factory.getBuildInfo();" + "platform = buildInfo.indexOf(';ie') > -1 ? 'IE' : platform;" + "platform = buildInfo.indexOf(';firefox') > -1 ? 'Firefox' : platform;" + "platform = buildInfo.indexOf(';safari') > -1 ? 'Safari' : platform;" + "platform = buildInfo.indexOf(';npapi') > -1 ? 'Npapi' : platform;" + "eventMap = {" + "'dragenter' : 'dragenter'," + "'dragover' : 'dragover'," + "'dragleave' : 'dragexit'," + "'drop' : 'dragdrop'" + "};" + "targetSWF = document.getElementById('" + ExternalInterface.objectID + "');" + "addEvent(targetSWF, 'dragenter', handleEnterOver);" + "addEvent(targetSWF, 'dragover', handleEnterOver);" + "addEvent(targetSWF, 'dragleave', handleLeave);" + "addEvent(targetSWF, 'drop', handleDrop);" + "initialized = true;" + "} catch(e) {" + "}" + "return initialized;" ); if(!ExternalInterface.call("dnd_init")) { view.lbl.htmlText = "<a href='http://gears.google.com/?action=install'>Gearsをインストールしてください</a>"; view.lbl.addEventListener( MouseEvent.CLICK, function(event:MouseEvent):void { navigateToURL(new URLRequest("http://gears.google.com/?action=install"), "_blank"); } ); } else { view.lbl.htmlText = "ここにドラッグ&ドロップしてください"; } } } }
上で使ってるFlexからjavascriptを書き込むユーティリティクラス。
JSUtil.as
package utils { import flash.external.ExternalInterface; public class JSUtil { public function JSUtil() { } public static function writeJS( js:String // javascriptコード ):Boolean { if (ExternalInterface.available == false) return false; try { ExternalInterface.call( "document.insertScript = " + js ); } catch(e:Error) { return false; } return true; } public static function writeJSFunction( functionName:String, // 関数名 functionArgments:Array, // 引数の配列 functionContent:String // 関数本体 ):Boolean { if (ExternalInterface.available == false) return false; try { var argName:Object; var argSep:String = ", "; var insertJS:String = "function () {" + "if(document. " + functionName + " == null) {" + functionName + " = function ("; if (functionArgments != null && functionArgments.length > 0) { for each(argName in functionArgments) { insertJS += argName.toString() + argSep; } insertJS = insertJS.substring(0, insertJS.lastIndexOf(argSep)); } insertJS += ") {" + functionContent + "}" + "}" + "}"; JSUtil.writeJS(insertJS); } catch(e:Error) { return false; } return true; } } }
これで、デスクトップ(エクスプローラ)からドラッグされたファイルを、
Flex側で受け取ることができるようになる。
何かGears使ってjavascript使って、と回りくどいやり方。
Flashが最初からサポートしてくれるといいのに。
プログラム、かけないなぁ
今回のエントリー、特にプログラムの話じゃないんですが。
なので、サンプルコードもありませんorz
少し気になったFlex関連のライブラリのメモです。
ほとんど(全部?)がid:sato-shiさんとこで紹介されてたものなので、
そっちで見た人のほうが多いはず。
まずはpavo。AIRでPDFを解析するライブラリ。
何かイロイロ使えそう。具体的にはわからんけど。
なんで、PDFもFlexもAdobeが出してるのに、こういうライブラリが本家から出ないんだろう?
次。Advanced AutoComplete。
名前のとおり、オートコンプリートができるテキストエリア。
すごい。こういうのがそろってくると、Flexでも実用的なものができそう(できる人なら)。
最後にDockableFlex。
簡単に言うと、iGoogleができます。つまりパネルをDnDして動かせる、と。
実は昔、こういうのを作ろうとしたことがある。
もちろん、こんなに完成度良くなかったけど。
それにしても、なかなか新しいエントリーが書けません。。。
もともと職業プログラマーではなく、仕事上プログラムを書く時はあまり無いんだが、今はまさに全く書かない時期。
ずっと机上で考え事と調べごとを行ったり来たり。
でもサンプルコードとか載せてる人、みんながみんな、職業プログラマというわけではないだろう。
時間の使い方が下手なのかな、やっぱり。。。
結局、愚痴って終わる始末w
外部SWFを表示する
GW前から、すっかりサボってしまいました。1週間ぶりの更新。
GWはETC割引に乗っかって、車で出かけてましたが、
大渋滞にはまったりで大変な目に(たぶん、誰でも予想できた事なんでしょうが)。
まぁ、これは別のお話なので、別のエントリーにするかも(しないかも)。
さて、今回はAIRで作ったアプリから、別のSWFを表示する例を。
これは、Java側で生成したSWFを、AIR側で表示するために必要な処理。
前のエントリーでAIRBootについてちょっとだけ触れたけど、必要な処理のヒント(というか全て)はそこのページに書いてある。
手順は、
- NativeWindowを作る
- Loaderを作って、1.で作ったNativeWindowに追加する
- ターゲットのSWFを、2.で作ったLoaderでロードする
これだけだったりする。
必要最小限のコードはこんな感じかと。
public function openSwfUrl(url:String):void { var initOptions:NativeWindowInitOptions = new NativeWindowInitOptions(); // …a // initOptions.maximizable = true; // initOptions.minimizable = true; // initOptions.resizable = true; // initOptions.systemChrome = NativeWindowSystemChrome.STANDARD; // initOptions.type = NativeWindowType.NORMAL; // initOptions.transparent = false; var window:NativeWindow = new NativeWindow(initOptions); // …b window.visible = true; // window.x; // window.y; // window.width; // window.height; // window.maxSize; // window.minSize; var loaderContext:LoaderContext = new LoaderContext(); loaderContext.allowLoadBytesCodeExecution = true; // …c window.stage.scaleMode = StageScaleMode.NO_SCALE; window.stage.align = StageAlign.TOP_LEFT; var loader:Loader = new Loader(); window.stage.addChild(loader); // …d var urlrequest:URLRequest = new URLRequest(); urlrequest.url = url; loader.load(urlrequest, loaderContext); // …e }
引数は開きたい外部SWFのURL。
NativeWindowのコンストラクタには、NativeWindowInitOptionsが必要なので、
それをあらかじめ作っておき(a)、好みに応じてパラメータをいじる。
次にNativeWindowを作り(b)、NativeWindow.visibleプロパティはtrueに設定。
その他のプロパティは好みに応じて。
LoaderContextを作り、allowLoadBytesCodeExecutionプロパティをtrueに設定(c)。
その下、2行も含めて、この辺はおまじない。
LoaderをNativeWindowに追加して(d)、URLRequestを引数にLoader.loadメソッドを実行すると(e)、
NativeWindowが表示され、指定した外部SWFが中に表示される。
以上の処理で、外部のあるSWFを動的にロードして表示することができるようになる。
一応、こういう処理パーツは出来上がっているんだけど、
アプリとしての仕上がりがまったく進まないんだよなぁ…。
参考URL
ブラウザ上でRuby/Pythonだって
最近見始めたてっく煮ブログ、
ブラウザ上でRubyを動かしたり、Pythonを動かしたりしている。
興味がある人は既にご存知かもしれませんが。
面白いねぇ〜。Silverlightだとこんなことが出来るのかぁ。
こちらはSilverlightはチンプンカンプンなので、Flexで出来ないかな。
JythonとかJRubyとかを駆使して、バックエンドでやるしかないのかな。
# キーワードで[Silverlight]なんて付けたけど、
# このエントリーだけかもねw
JavaとFlexの連携その3(やっとつながる)
前回は前準備だけで終わってしまったので、今回ようやく繋げる。
まず前回のエントリーで、任意のサーブレットをJettyに組み込めたので、
これにBlazeDSを組み込んでみる。
BlazeDSはTomcatとセットで配布されているバージョンもあり、
Web上のサンプルはそれをとりあえず動かしてみる、みたいのが多かったが、
自分のWebアプリにBlazeDSでのメッセージング/リモーティング機能を組み込むサンプルをやっと見つけた。
必要なファイルにクラスパスを通し、前回のエントリーのコードを以下のように修正した
// サーブレットを作成 // 「MessageBrokerServlet」を使用 ServletHolder messageBrokerServletHolder = new ServletHolder(MessageBrokerServlet.class); // サーブレットに初期パラメータを渡す場合は、以下の処理でできる // まずはHashMapで初期パラメータを作成 HashMap<String, String> servletInitMap = new HashMap<String, String>(); servletInitMap.put( "services.configuration.file", "/WEB-INF/flex/services-config.xml" ); // サーブレットに初期パラメータ用HashMapで設定 messageBrokerServletHolder.setInitParameters(servletInitMap); Server server = new Server(); // Serverオブジェクトにコネクタを設定 server.addConnector(connector); Context hogeContext = new Context(server, "/", Context.SESSIONS); // サーブレットを追加 // "/messagebroker"というパスにmessageBrokerServletHolderを割り当てる hogeContext.addServlet(messageBrokerServletHolder, "/messagebroker/*");
通常ではweb.xmlで設定するBlazeDSの初期パラメータ("services.configuration.file")を、
あらかじめ作っておいたBlazeDS用の設定XMLファイル("/WEB-INF/flex/services-config.xml")を指定して、
HashMapに格納して設定してる。
で、実行してみると、怒られた。
上で設定したXMLファイルが見つからないらしい。
イロイロ設定を試したが、やっぱりファイルが見つからない。
# 実は、ここで一度BlazeDSの使用は見送り、
# JettyのContinuation機能を使って独自にCometを実装したが、
# あまりにショボイ実装のため不具合多発、結局再度BlazeDSに戻ってきた
さて設定ファイルをXMLファイルにして、それをうまく指定するには何かわからない設定があるらしい。
なので、BlazeDSがXMLファイルを読んだ後に、どういう処理をやっているのか、を調べるため
BlazeDSのソースを追っかけてみた。
すると、"services.configuration.manager"という初期パラメータが浮かび上がってきた。
Web上のサンプルではそれほどみないパラメータ。
これを指定しない(つまり一般的なサンプル)と、デフォルトでflex.messaging.config.FlexConfigurationManagerというクラスが使用され、
このflex.messaging.config.FlexConfigurationManagerクラスが
という流れらしい。
なるほど、じゃあ、FlexConfigurationManagerの代わりを作って、そいつを"services.configuration.manager"で指定するといいんじゃね?
と言うわけでやってみた。
まずHashMapに追加する値を書き換える。
HashMap<String, String> servletInitMap = new HashMap<String, String>(); servletInitMap.put( "services.configuration.manager", "CustomFlexConfigurationManager" );
次にCustomFlexConfigurationManagerを実装して、
XMLファイルで記述する内容をそのままJavaでハードコードする。
# 今回はハードコードで十分なのですm(__)m
package org.pollux.messaging.config; import javax.servlet.ServletConfig; import flex.messaging.config.AdapterSettings; import flex.messaging.config.ChannelSettings; import flex.messaging.config.ConfigMap; import flex.messaging.config.DestinationSettings; import flex.messaging.config.FlexConfigurationManager; import flex.messaging.config.LoggingSettings; import flex.messaging.config.LoginCommandSettings; import flex.messaging.config.MessagingConfiguration; import flex.messaging.config.ServiceSettings; import flex.messaging.config.SystemSettings; import flex.messaging.config.TargetSettings; public class CustomFlexConfigurationManager extends FlexConfigurationManager { @Override public MessagingConfiguration getMessagingConfiguration(ServletConfig servletConfig) { // TODO Auto-generated method stub MessagingConfiguration config = new MessagingConfiguration(); // ここで、BlazeDSの設定(services-config.xmlに書いてあるやつ)を作れば、 // XML設定ファイル群は不要になる ChannelSettings msgChannelSettings = new ChannelSettings("my-polling-amf"); msgChannelSettings.setClientType( "mx.messaging.channels.AMFChannel" ); msgChannelSettings.setEndpointType( "flex.messaging.endpoints.AMFEndpoint" ); msgChannelSettings.setUri( "http://{server.name}:{server.port}/messagebroker/pollingamf" ); ConfigMap propertyMap = new ConfigMap(); propertyMap.addProperty("polling-enabled", "true"); propertyMap.addProperty("polling-interval-millis", "0"); propertyMap.addProperty("wait-interval-millis", "-1"); propertyMap.addProperty("max-waiting-poll-requests", "300"); msgChannelSettings.addProperties(propertyMap); config.addChannelSettings("my-polling-amf", msgChannelSettings); SystemSettings ss = new SystemSettings(); ss.setRedeployEnabled("false"); config.setSystemSettings(ss); LoggingSettings loggingSettings = new LoggingSettings(); TargetSettings t = new TargetSettings("flex.messaging.log.ConsoleTarget"); t.setLevel("Error"); t.addProperty("prefix","[BlazeDS]"); t.addProperty("includeDate","false"); t.addProperty("includeTime","false"); t.addProperty("includeLevel","false"); t.addProperty("includeCategory","false"); t.addFilter("Endpoint.*"); t.addFilter("Service.*"); t.addFilter("Configuration"); loggingSettings.addTarget(t); config.setLoggingSettings(loggingSettings); ServiceSettings messageSettings = new ServiceSettings("message-service"); messageSettings.setClassName("flex.messaging.services.MessageService"); AdapterSettings adptSetting = new AdapterSettings("actionscript"); adptSetting.setClassName( "flex.messaging.services.messaging.adapters.ActionScriptAdapter" ); adptSetting.setDefault(true); messageSettings.addAdapterSettings(adptSetting); messageSettings.addDefaultChannel(msgChannelSettings); DestinationSettings dstSetting = new DestinationSettings("actionscript-message"); dstSetting.setAdapterSettings(adptSetting); messageSettings.addDestinationSettings(dstSetting); config.addServiceSettings(messageSettings); LoginCommandSettings lcs = new LoginCommandSettings(); lcs.setClassName("flex.messaging.security.TomcatLoginCommand"); lcs.setServer("Tomcat"); config.getSecuritySettings().addLoginCommandSettings(lcs); config.getSecuritySettings().setServerInfo( servletConfig.getServletContext().getServerInfo() ); return config; } @Override public void reportTokens() { // TODO Auto-generated method stub } }
ちなみに以下のXMLファイルと同じ設定になる
<?xml version="1.0" encoding="UTF-8"?> <services-config> <services> <service id="message-service" class="flex.messaging.services.MessageService"> <adapters> <adapter-definition id="actionscript" class="flex.messaging.services.messaging.adapters.ActionScriptAdapter" default="true" /> </adapters> <default-channels> <channel ref="my-polling-amf" /> </default-channels> <destination id="actionscript-message"> <adapter ref="actionscript"/> </destination> </service> </services> <security> <login-command class="flex.messaging.security.TomcatLoginCommand" server="Tomcat" /> </security> <channels> <channel-definition id="my-polling-amf" class="mx.messaging.channels.AMFChannel"> <endpoint url="http://{server.name}:{server.port}/messagebroker/amfpolling" class="flex.messaging.endpoints.AMFEndpoint" /> <properties> <polling-enabled>true</polling-enabled> <polling-interval-millis>0</polling-interval-millis> <wait-interval-millis>-1</wait-interval-millis> <max-waiting-poll-requests>300</max-waiting-poll-requests> </properties> </channel-definition> </channels> <logging> <target class="flex.messaging.log.ConsoleTarget" level="Error"> <properties> <prefix>[BlazeDS] </prefix> <includeDate>false</includeDate> <includeTime>false</includeTime> <includeLevel>false</includeLevel> <includeCategory>false</includeCategory> </properties> <filters> <pattern>Endpoint.*</pattern> <pattern>Service.*</pattern> <pattern>Configuration</pattern> </filters> </target> </logging> <system> <redeploy> <enabled>false</enabled> </redeploy> </system> </services-config>
これで組み込みJetty上でBlazeDSが動作する(はず)。
ここまでやったサンプルはあまりWebで見ないので(需要がない、と言うことか。。。)、
今回は頑張って調べました。
参考サイト