ReverseHTTPを使ってみる

2ヶ月ぶりだろうか、エントリーを追加するのは。
その間、本業の方が洒落にならないくらい忙しく、趣味プログラミングは殆どできなかった。

(サンプルコードを見れば想像できると思うけど)本業はプログラマー/SEではないので、
開発環境には殆ど触れない生活ばかり。

そんな中お盆休みに入り、多少時間にゆとりができたので、
ドラクエ9に没頭する相方を尻目に、なにやらちょっとした騒ぎがあるReverseHTTPをテーマに自宅で少しプログラムをしてみた。

以前のエントリーでぶち上げたウィジェットマネージャは、αレベルくらいなものは出来上がってるんだけど、
思いついたら機能を追加、という形だったので、設計がひど過ぎて、脳内再設計に入りかけたところで、
本業多忙になってしまい、殆ど手付かずなんだけどねw

で、ReverseHTTP

気分転換にイロイロWebを見てると、こういうところで知ったのがきっかけ。


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のjavascriptFlexを繋げてる。
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');"
            );
            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さんとこで紹介されてたものなので、
そっちで見た人のほうが多いはず。


まずはpavoAIRでPDFを解析するライブラリ。
何かイロイロ使えそう。具体的にはわからんけど。
なんで、PDFもFlexAdobeが出してるのに、こういうライブラリが本家から出ないんだろう?


次。Advanced AutoComplete
名前のとおり、オートコンプリートができるテキストエリア。
すごい。こういうのがそろってくると、Flexでも実用的なものができそう(できる人なら)。


最後にDockableFlex
簡単に言うと、iGoogleができます。つまりパネルをDnDして動かせる、と。
実は昔、こういうのを作ろうとしたことがある。
もちろん、こんなに完成度良くなかったけど。


それにしても、なかなか新しいエントリーが書けません。。。
もともと職業プログラマーではなく、仕事上プログラムを書く時はあまり無いんだが、今はまさに全く書かない時期。
ずっと机上で考え事と調べごとを行ったり来たり。

でもサンプルコードとか載せてる人、みんながみんな、職業プログラマというわけではないだろう。
時間の使い方が下手なのかな、やっぱり。。。


結局、愚痴って終わる始末w

外部SWFを表示する

GW前から、すっかりサボってしまいました。1週間ぶりの更新。


GWはETC割引に乗っかって、車で出かけてましたが、
大渋滞にはまったりで大変な目に(たぶん、誰でも予想できた事なんでしょうが)。
まぁ、これは別のお話なので、別のエントリーにするかも(しないかも)。


さて、今回はAIRで作ったアプリから、別のSWFを表示する例を。
これは、Java側で生成したSWFを、AIR側で表示するために必要な処理。


前のエントリーでAIRBootについてちょっとだけ触れたけど、必要な処理のヒント(というか全て)はそこのページに書いてある。


手順は、

  1. NativeWindowを作る
  2. Loaderを作って、1.で作ったNativeWindowに追加する
  3. ターゲットの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

キツ…

正直、しんどい(そんなテレビ番組がありましたな)。


本業(会社員なんですが)が迷走中。メンバーを減らされ、先行きが見えない。
そんな状態なので、プログラミングは手付かずなんです。


私生活も、相方とピリピリ状態。


GWはリフレッシュできるかなぁ。。。


以上、愚痴でした。

ブラウザ上でRuby/Pythonだって

最近見始めたてっく煮ブログ
ブラウザ上でRubyを動かしたりPythonを動かしたりしている。
興味がある人は既にご存知かもしれませんが。


面白いねぇ〜。Silverlightだとこんなことが出来るのかぁ。


こちらはSilverlightはチンプンカンプンなので、Flexで出来ないかな。
JythonとかJRubyとかを駆使して、バックエンドでやるしかないのかな。


# キーワードで[Silverlight]なんて付けたけど、
# このエントリーだけかもねw

JavaとFlexの連携その3(やっとつながる)

前回は前準備だけで終わってしまったので、今回ようやく繋げる。


まず前回のエントリーで、任意のサーブレットをJettyに組み込めたので、
これにBlazeDSを組み込んでみる。
BlazeDSTomcatとセットで配布されているバージョンもあり、
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ファイルにして、それをうまく指定するには何かわからない設定があるらしい。
なので、BlazeDSXMLファイルを読んだ後に、どういう処理をやっているのか、を調べるため
BlazeDSのソースを追っかけてみた。
すると、"services.configuration.manager"という初期パラメータが浮かび上がってきた。


Web上のサンプルではそれほどみないパラメータ。
これを指定しない(つまり一般的なサンプル)と、デフォルトでflex.messaging.config.FlexConfigurationManagerというクラスが使用され、
このflex.messaging.config.FlexConfigurationManagerクラスが

  1. "services.configuration.file"で指定される設定用XMLファイルを読み込む
  2. XMLファイルに従ってメッセージング/リモーティングの設定を行う

という流れらしい。


なるほど、じゃあ、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で見ないので(需要がない、と言うことか。。。)、
今回は頑張って調べました。

参考サイト