sobota, 2 lipca 2011

Simple http push server for flash using python twisted

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 :)

Brak komentarzy:

Publikowanie komentarza