2011
07.02

I was preety busy at work. Sorry for not posting. I will try to post once a month from now.
The networing profiler project was stopped due to my work but I will try to finish it’s simple and post the sources. To the point.

In my workplace we are pushing some data to the clients, and more websites are pushing realtime data so it made me think. How hard it’s to write simple http push server for actionscript using python ?

Well it seems not so hard cause we got great library for it twisted. So let’s get started on server side:

We will be using twisted web server and Site object,JSON to send data, simplejson library to convert our objects to JSON

So we start by creating simple class Test with GET and POST handlers and in get we add server.NOT_DONE_YET to never stop sending GET request once connected. In __init__ method we declare array for users and array for messages to pass. Next we add LoopingCall task to throtle messages for our clients every 250ms, and define method __print_time that we will be using to create messages. Well __print_time is simple method that we pass time and messages when there are any in our messages array.
We simply loop in our users array and add aditional message to send.

Things to improve:
1. detect when user disconnect
2. add user id when connected
3. replace json with amf protocol

from twisted.internet import reactor, task
from twisted.web.server import Site
from twisted.web import server
from twisted.web.resource import Resource
 
import simplejson as json
import time
 
class Test(Resource):
    isLeaf = True
    def __init__(self):
        self.json_encoder = json.JSONEncoder()
        self.users=[]
        self.messages = []
        loopingCall = task.LoopingCall(self.__print_time)
        loopingCall.start(.25, False)
        Resource.__init__(self)
 
    def render_GET(self, request):
        request.write(self.json_encoder.encode({"time":time.ctime(), "type":"CONNECT"}))
        print "client connected"
        self.users.append(request)
        print "number clients:", len(self.users);
        return server.NOT_DONE_YET
 
    def render_POST(self, request):
        if request.args["msg"]:
            self.messages.append(request.args);
        print request.args
 
    def __print_time(self):
        sendMSG = None
        while len(self.messages) > 0:
            sendMSG=self.messages.pop();
        # keep alive connection
        for p in self.users:
            p.write(self.json_encoder.encode({"time":time.time(), "msg":[sendMSG]}))
 
print "server start"
resource = Test()
factory = Site(resource)
reactor.listenTCP(8081, factory)
reactor.run()
print "server stop"

Next what about client, some time ago I started simple library for my inhouse projects.
I created simple HTTPConnection class where I use URLStream for handling connection and some self created Observer for listening events instead of eventDispatcher.
Well I will not describe my classes here but only show the main class for the server class in python.
The main method is init where we create gui, HTTPConnection named consumer and some logger. We add event for progress where we simply try to parse json using as3corelib and if we have some messages we try to display it in our gui. when we press ENTER in our upper TextField we are simply sending post to the same url.

Things to improve:
1. detect when disconnected and try to reconnect
2. parse messages only when we need

package
{
	import clazz.GUI;
 
	import com.adobe.serialization.json.JSON;
 
	import flash.display.Sprite;
	import flash.events.DataEvent;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.utils.ByteArray;
 
	import pl.vane.framework.errors.Throws;
	import pl.vane.framework.logging.ILog;
	import pl.vane.framework.logging.Log;
	import pl.vane.framework.net.HTTPConnection;
	import pl.vane.framework.net.IConnection;
 
	public class Main extends Sprite
	{
		public function Main()
		{
			if(!stage)
				addEventListener(Event.ADDED_TO_STAGE, init);
			else
				init();
		}
 
		private var _gui:GUI;
 
		private var _consumer:IConnection;
 
		private var _log:ILog;
 
		protected function init(event:Event = null):void
		{
			_gui = new GUI;
			_gui.addEventListener(DataEvent.DATA, dataSendHandler);
			addChild(_gui);
 
			_consumer = new HTTPConnection;
			_consumer.observer.addListener(Event.COMPLETE, connectionCompleteHandler);
			_consumer.observer.addListener(ProgressEvent.PROGRESS, connectionProgressHandler);
			_consumer.load(new URLRequest("http://127.0.0.1:8081"));
 
			_log = Log.getLogger(this);
			_gui.appendText = "start\n";
			_log.debug("start");
		}
 
		protected function dataSendHandler(event:DataEvent):void
		{
			// TODO Auto-generated method stub
			var c:IConnection = new HTTPConnection(true)
			c.observer.addListener(Event.COMPLETE, function complete(data:ByteArray):void
			{
				_log.debug(data.readUTFBytes(data.bytesAvailable));
			});
			var r:URLRequest = new URLRequest("http://127.0.0.1:8081");
			r.data = encodeURI("msg=" + event.data);
			r.method = URLRequestMethod.POST;
			c.load(r);
		}
 
		protected function connectionProgressHandler(data:ByteArray):void
		{
			// TODO Auto Generated method stub
			var next:String = data.readUTFBytes(data.bytesAvailable);
 
			var o:Object;
			try
			{
				o = JSON.decode(next);
			} catch(e:Error)
			{
				Throws.error(e);
			}
			if(o)
			{
				_log.track(o.time);
				if(o.msg)
				{
					if(o.msg[0] && o.msg[0].msg)
					{
						_gui.appendText = o.msg[0].msg+"\n";
					}
					_log.info(o.msg);
				}
				//chat.text = next+ "\n" + chat.text;
			}
			trace("DUPA:"+data);
		}
 
		protected function connectionCompleteHandler(data:ByteArray):void
		{
			// TODO Auto Generated method stub
			_log.debug("data" + data.readUTFBytes(data.bytesAvailable));
		}
	}
}

And the gui class for display the messages:

package clazz
{
	import flash.display.Sprite;
	import flash.events.DataEvent;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.text.TextField;
	import flash.text.TextFieldType;
	import flash.ui.Keyboard;
 
	[Event(name="data", type="flash.events.DataEvent")]
 
	public class GUI extends Sprite
	{
		private var _txt:TextField;
		private var _msg:TextField;
 
		public function GUI()
		{
			super();
			if(!stage)
				addEventListener(Event.ADDED_TO_STAGE, init);
			else
				init();
		}
 
		protected function init(event:Event = null):void
		{
			if(!_txt)
			{
				_txt = new TextField;
				_txt.type = TextFieldType.DYNAMIC;
				addChild(_txt);
			}
			if(!_msg)
			{
				_msg = new TextField;
				_msg.type = TextFieldType.INPUT;
				_msg.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
				_msg.border = true;
				addChild(_msg);
			}
			resizeHandler();
			stage.addEventListener(Event.RESIZE, resizeHandler);
		}
 
		protected function keyDownHandler(event:KeyboardEvent):void
		{
			if(event.keyCode == Keyboard.ENTER)
			{
				dispatchEvent(new DataEvent(DataEvent.DATA, false, false, _msg.text));
				_msg.text = "";
			}
 
		}
 
		public function set appendText(value:String):void
		{
			_txt.text = value+_txt.text;
		}
 
		protected function resizeHandler(event:Event = null):void
		{
			_txt.x = 0;
			_txt.y = 50;
			_txt.width = stage.stageWidth;
			_txt.height = stage.stageHeight - 50;
 
			_msg.x = 0;
			_msg.y = 0;
			_msg.width = stage.stageWidth;
			_msg.height = 50;
		}
	}
}

And that’s it. The exported swf size is wow 12KB, not much :)
Any questions please post in comments.
Sources are below, i will try to post my HTTPConnection class sources with others on github as soon as I will have some more time.
Just run twisted_stream.py in server folder and Main.swf in client/bin-release

twistedstream

Have fun :)

2011
04.10

Well I got a problem with registerClassAlias and that was all because I didn’t read the manual, the whole thing is working now. Thanks to the awesome project flashlcs - Unlimited 2 way local runtime data messaging for the Flash Platform. that I visited one more time this week. And they are mentioning to read the manual page on their main page.
So from the AS3 manual page correct version of registerClassAlias is:

package {
    import flash.display.Sprite;
    import flash.net.registerClassAlias;
    import flash.utils.ByteArray;
 
    public class RegisterClassAliasExample extends Sprite {
        public function RegisterClassAliasExample() {
            registerClassAlias("com.example.eg", ExampleClass);
            var eg1:ExampleClass = new ExampleClass();
            var ba:ByteArray = new ByteArray();
            ba.writeObject(eg1);
            ba.position = 0;
            var eg2:* = ba.readObject();
            trace(eg2 is ExampleClass); // true
        }
    }
}
 
class ExampleClass {}

But you need to remember when you define the ExampleClass with constructor to not leave the constructor arguments not set otherwise you will get the null pointer exception when reading the object back ex.

// correct
class ExampleClass
{
 function ExampleClass(someVar:Object = null, someVar2:String=null)
 {
 }
}
 
// incorrect
// will throw Exception when ba.readObject();
class ExampleClass
{
 function ExampleClass(someVar, someVar2)
 {
 }
}

so now I can write and read my objects between my AIR socket server and AS project. I also have some kind of protocol to send large portions of data working.

Also When You are using debugger version of the flashplayer you can force to run GC yourself just by calling

System.gc()

and to invoke it manually Grant Skinner on his blog gave you a tip some time ago. So when I detect that user have no debugger version of flashplayer I simply invoke LocalConnection trick. The example method is:

public function runGC():void
{
	if(Capabilities.isDebugger)
	{
		System.gc();
		System.gc();
	} else
	{
		try
		{
			new LocalConnection().connect("so");
			new LocalConnection().connect("so");
		} catch(e:Error)
		{
			log.error(e.getStackTrace());
		}
	}
}

Well that’s it for now. Next I will try to have some fun with the

flash.sampler.Sample