RkBlog

Hardware, programming and astronomy tutorials and reviews.

Comet i Orbited

Comet to architektura www, w kt贸rej serwer wysy艂a do klienta (np przegl膮darki) dane, a wszystko to asynchronicznie i bez bezpo艣redniego 偶膮dania klienta. Umo偶liwia to tworzenie aplikacji internetowych dzia艂aj膮cych na zdarzeniach. Comet wykorzystywany jest w takich aplikacjach jak Gmail/GTalk i coraz cz臋艣ciej wita w aplikacjach mniejszego kalibru (django-dose, grono.net).

W Pythonie dost臋pne s膮 dwa rozwi膮zania umo偶liwiaj膮ce wykorzystanie tej architektury - Divmod Athena oraz Orbited, kt贸remu po艣wi臋cony jest ten artyku艂. Orbited to serwer Comet - dzia艂aj膮cy na zdarzeniach, a nie jak zwyk艂y serwer HTTP w oparciu o 偶膮dania. Posiada obecnie API dla Pythona, Ruby i PHP.

Instalacja

Zale偶no艣ci (Linux): Cz臋艣膰 zale偶no艣ci jak Twisted mo偶emy zainstalowa膰 z pakiet贸w dystrybucji. Orbited i jego zale偶no艣ci powinny zainstalowa膰 si臋 bez problemu poprzez:
easy_install orbited

Konfiguracja serwera orbited

By m贸c skorzysta膰 z serwera orbited musimy stworzy膰 plik konfiguracyjny i odpali膰 serwer wskazuj膮c mu ten plik. Z poziomu zwyk艂ego u偶ytkownika stw贸rz pusty katalog, a w nim plik konfiguracyjny, np. graph.cfg o kodzie:
[listen]
http://:9000
stomp://:61613
 
[access]
* -> localhost:61613
 
[static]
graph=index.html
 
[global]
session.ping_interval = 300
Konfiguracja podzielona jest na kilka sekcji.

Powy偶sza konfiguracja mo偶e pos艂u偶y膰 jako punkt wyj艣cia w r贸偶nych kometowych aplikacjach. W naszym przypadku stworzymy prost膮 stron臋 HTML - index.html, kt贸ra zademonstruje dzia艂anie tego serwera i technologii.

Zapisz plik konfiguracji i stw贸rz plik index.html z np. dowolnym tekstem "powitalnym". By uruchomi膰 serwer Orbited w konsoli wykonaj (w katalogu z konfiguracj膮):
orbited --config graph.cfg
Serwer powinien wystartowa膰:
orbited --config graph.cfg
03/03/10 20:11:38:297 INFO   orbited.start      proxy protocol active
03/03/10 20:11:38:306 INFO   orbited.start      Listening http@9000
03/03/10 20:11:38:307 INFO   orbited.start      Listening stomp@61613
Pod adresem http://localhost:9000/graph powinni艣my zobaczy膰 zawarto艣膰 pliku index.html.

Wykorzystujemy Cometa

Serwer Comet taki jak Orbited dzia艂a inaczej ni偶 serwer HTTP. Wchodz膮c na stron臋, kt贸ra korzysta z tej technologii nast臋puje nawi膮zanie po艂膮czenia z serwerem. Mimo i偶 strona zosta艂a za艂adowana - po艂膮czenie jest utrzymywane w tle i serwer mo偶e wysy艂a膰 dane do przegl膮darki w dowolnym momencie (np. pod wp艂ywem jakiego艣 zdarzenia). Przypomina to zasad臋 dzia艂ania internetowego czata - 艂膮czymy si臋 i czekamy na nap艂ywaj膮ce wiadomo艣ci.

Teraz zaprezentujmy Cometa w akcji. W tym przyk艂adzie stworzymy prosty wykres, kt贸ry b臋dzie zmienia艂 si臋 dynamicznie pod wp艂ywem danych wysy艂anych przez serwer. 呕eby to wszystko dzia艂a艂o musimy do艂膮czy膰 do strony HTML magiczne biblioteki JavaScript dostarczane automatycznie wraz z Orbited:
<script src="http://localhost:9000/static/Orbited.js"></script>
<script>
TCPSocket = Orbited.TCPSocket
</script>
<script src="http://localhost:9000/static/protocols/stomp/stomp.js"></script>
Teraz zr贸bmy wykresy za pomoc膮 JavaScriptu. Poni偶szy kod nie jest powi膮zany z Orbited, po prostu wrzuca 10 "s艂upk贸w" do kontenera (DIV):
var bars = [];
var num_bars = 10;
 
// Make ten bars that are blue
var init_graph = function(num_bars, bars, container_name) {
 
    var container = document.getElementById(container_name);
    for (var i=0; i< num_bars; i++) {
        var bar = document.createElement('div');
        bar.className = "bar";
        bar.style.width = "100px";
        bars.push(bar);
        container.appendChild(bar);
    }
    // some pizazz... one bar gets to be red.
    bars[3].style.backgroundColor = "red";
};
Teraz nieco trudniejsza cz臋艣膰, czyli kod JS modyfikuj膮cy wykres po otrzymaniu danych:
// Change the length of the bars to match given numerical data
var modify_graph = function(bars, payload) {
    var vals = JSON.parse(payload);
    for (var i=0; i<bars.length; i++) {
        var bar = bars[i];
        bar.style.width = vals[i] + "px";
    }
}
// In production use your js toolkit's onload system, or event listeners
onload = function() {
    init_graph(num_bars, bars, "container");
 
    stomp = new STOMPClient();
    stomp.onopen = function() {
    };
    stomp.onclose = function(c) { alert('Lost Connection, Code: ' + c);};
    stomp.onerror = function(error) {
        alert("Error: " + error);
    };
    stomp.onerrorframe = function(frame) {
        alert("Error: " + frame.body);
    };
    stomp.onconnectedframe = function() {
        stomp.subscribe("/topic/graph");
    };
    stomp.onmessageframe = function(frame) {
        // Presumably we should only receive message frames with the 
        // destination "/topic/graph" because thats the only destination 
        // to which we've subscribed. To handle multiple destinations we 
        // would have to check frame.headers.destination.
        modify_graph(bars, frame.body);
    };
    stomp.connect('localhost', 61613);
}
W onload wykorzystujemy JavaScriptowe API STOMPa, by nawi膮za膰 po艂膮czenie Comet przy otwieraniu strony i obs艂ugiwa膰 nadchodz膮ce wiadomo艣ci (zdarzenia) dzi臋ki metodzie onmessageframe, kt贸ra wywo艂a zwyk艂膮 funkcj臋 modyfikuj膮c膮 wykres. Nasz index.html wraz z prostymi stylami i DIVem - kontenerem powinien wygl膮da膰 teraz tak:
<!DOCTYPE html>
<head>
<title>Orbited + Morbid + js.io Graph</title>
<script src="http://localhost:9000/static/Orbited.js"></script>
<script>
TCPSocket = Orbited.TCPSocket
</script>
<script src="http://localhost:9000/static/protocols/stomp/stomp.js"></script>
<script>
var bars = [];
var num_bars = 10;
 
// Make ten bars that are blue
var init_graph = function(num_bars, bars, container_name) {
 
    var container = document.getElementById(container_name);
    for (var i=0; i< num_bars; i++) {
        var bar = document.createElement('div');
        bar.className = "bar";
        bar.style.width = "100px";
        bars.push(bar);
        container.appendChild(bar);
    }
    // some pizazz... one bar gets to be red.
    bars[3].style.backgroundColor = "red";
};
// Change the length of the bars to match given numerical data
var modify_graph = function(bars, payload) {
    var vals = JSON.parse(payload);
    for (var i=0; i<bars.length; i++) {
        var bar = bars[i];
        bar.style.width = vals[i] + "px";
    }
}
// In production use your js toolkit's onload system, or event listeners
onload = function() {
    init_graph(num_bars, bars, "container");
 
    stomp = new STOMPClient();
    stomp.onopen = function() {
    };
    stomp.onclose = function(c) { alert('Lost Connection, Code: ' + c);};
    stomp.onerror = function(error) {
        alert("Error: " + error);
    };
    stomp.onerrorframe = function(frame) {
        alert("Error: " + frame.body);
    };
    stomp.onconnectedframe = function() {
        stomp.subscribe("/topic/graph");
    };
    stomp.onmessageframe = function(frame) {
        // Presumably we should only receive message frames with the 
        // destination "/topic/graph" because thats the only destination 
        // to which we've subscribed. To handle multiple destinations we 
        // would have to check frame.headers.destination.
        modify_graph(bars, frame.body);
    };
    stomp.connect('localhost', 61613);
}
</script>
<style>
    body, html, h1 {
        margin: 0px;
        padding: 0px;
        font-family: sans-serif;
        background-color: #f3ffe2;
    }
 
    .bar {
        background-color: #38f;
        height: 12px;
        width: 200px;
        margin: 3px 0px;
    }
 
    #container {
        margin: auto;
        border: 2px solid #000;
        width: 400px;
        background-color: #fff;
    }
</style>
</head>
<body>
<div id="container"></div>
</body>
</html>
Strona powinna wygl膮da膰 obecnie tak:
new_orbited1
Mamy za艂atwiony frontend, wi臋c trzeba zaj膮c si臋 backendem. Wykorzystamy prosty skrypt w Pythonie do generowania wiadomo艣ci na serwerze:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from stompservice import StompClientFactory
from twisted.internet import reactor
from twisted.internet.task import LoopingCall
from random import random
from orbited import json
 
DATA_VECTOR_LENGTH = 10
DELTA_WEIGHT = 0.1
MAX_VALUE = 400 # NB: this in pixels
CHANNEL_NAME = "/topic/graph"
INTERVAL = 1000 # in ms
 
class DataProducer(StompClientFactory):
 
    def recv_connected(self, msg):
        print 'Connected; producing data'
        self.data = [ 
            int(random()*MAX_VALUE) 
            for 
            x in xrange(DATA_VECTOR_LENGTH)
        ]
        self.timer = LoopingCall(self.send_data)
        self.timer.start(INTERVAL/1000.0)
 
    def send_data(self):
        # modify our data elements
        self.data = [ 
            min(max(datum+(random()-.5)*DELTA_WEIGHT*MAX_VALUE,0),MAX_VALUE)
            for 
            datum in self.data
        ]
        self.send(CHANNEL_NAME, json.encode(self.data))
 
reactor.connectTCP('localhost', 61613, DataProducer())
reactor.run()
Wykorzystujemy tutaj Twisted oraz API STOMPa. Odpal skrypt w przegl膮darce i obserwuj zmiany wykres贸w w przegl膮darce. Wykresy b臋d膮 losow zmienia膰 swoje warto艣ci:
new_orbited2
Powy偶szy przyk艂ad oparty na artykule z cometdaily.com.

Co dalej?

Warto zaznajomi膰 si臋 z:
RkBlog

Programowanie Sieciowe, 4 August 2011, Piotr Mali艅ski

Comment article