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):- simplejson
- demjson
- Twisted
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
- W sekcji [listen] określamy porty jakie chcemy wykorzystywać. Na porcie 9000 oczekujemy połączeń sieciowych HTTP, oraz połączeń usługi stomp (element orbited) na porcie 61613
- Sekcja [access] pozwala przekazywać (proxować) połączenia tcp użytkowników na port stompa - 61613.
- Najważniejsza sekcja - [static] określa listę wirtualnych nazw i pliki im odpowiadające. W powyższym przykładzie pod adresem "/graph" zostanie wyświetlony plik index.html z tego samego katalogu, w którym znajduje się konfiguracja. W warunkach produkcyjnych strony aplikacji, czy pliki statyczne mogą być obsługiwane przez zwykły serwer HTTP. Sekcja static przydaje się przy początkowym tworzeniu aplikacji, gdzie za pomocą serwera Orbited możemy odpalić także stronę wykorzystującą Cometa.
- Sekcja [global] zawieraj konfigurację pingowania klienta przez serwer. Co 300 sekund serwer będzie sprawdzał, czy klient nadal jest dostępny.
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 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
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>
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);
}
<!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>

#!/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()

Co dalej?
Warto zaznajomić się z:- Dokumentacją
- Stronami wykorzystującymi Orbited
- Artykułami poświęconymi Cometowi/Orbited
- Prezentacją o LeafyChat i Django-Dose opartymi o Cometa
Comment article