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に対応してくれないと、使い方に困るかもなぁ。