(Evil Twins)² – Kommunikation mit CoffeeScript/JavaScript und jQuery/Rx

Wer mich kennt, weiß das ich ein kleiner “Software Language Geek” bin. Ich sehe mir gerne andere, oder neue Programmiersprachen an, ohne dafür einen konkreten Grund haben zu müssen. Einfach weil mich die Konzepte interessieren und es mir Spaß macht. Ich habe mich lange Zeit gegen JavaScript gewehrt, weil mir die Syntax nicht gefallen hat. Gut, das kleine bisschen an Fähigkeiten, was ich bisher gebraucht habe ist nicht der Rede wert. Das sollte jeder Software Entwickler/Ingenieur nach 14 Jahren hinbekommen. Was mich allerdings schon immer an JavaScript interessiert hat waren die Eigenschaften aus der Funktionalen Programmierung. Spätestens seit dem ich mich mit F# (also seit ca. 3 Jahren) angefreundet habe sieht es da um die Zuneigung zu JavaScript besser aus.

Motivation

Über die Weihnachtsferien habe ich mich dann hingesetzt und mir CoffeeScript nochmal genauer angeschaut. Ich hatte mir vor Urzeiten ohne fundiertes Wissen ein Vorurteil über JavaScript gebildet. Damit wollte ich schon lange aufräumen. Ich bin ja offen für andere Gedanken und auch Dankbar für neue Perspektiven.

Ich arbeite beruflich die meiste Zeit mit Python, seit neuestem ein bischen PHP Backend (die 2,5 Jahre vor diesem Sommer auch oft einmal kleine Tools/Skripte mit F# oder C#). Da ich ich in Python und Web-Entwicklung etwas mehr Zeit investieren wollte um schnelle Protottypen zu entwickeln habe ich nach einer guten Architektur zum Kommunizieren gesucht. Dar Hauptfokus lag hier bei Desktop Prototypen die bei Bedarf ins Web skalieren können.

Anforderungen

  1. Klein (wenig Code)
  2. Schnell
    1. Entwicklungszeit
    2. Antwortzeit der Architektur
  3. Günstig
    1. keine teure Technologie
    2. einfach zu verteilen
    3. am besten Open Source
  4. Skalierbar und Kommunikationsfreudig
    1. im Sinne der Verteilbarkeit, nicht der Nutzerlast

Entwurf

Da ich mit Python die letzten Jahre sehr gute Erfahrungen gemacht habe was die ersten 3 Punkte angeht, wollte ich auch erst einmal dabei bleiben. Für die Kommunikation sollten es WebSockets werden. Alter Hut (Stichwort Comet), lange durchgekaut und noch immer nicht ganz fertig.So ein bischen erinnert mich HTML5 ja an C++0x. Trotzdem auf dem besten Weg. Vor allem ging es mir dabei um Bidirektionale Kommunikation ohne Hacks a la Long Polling. Zuerst wollte ich ja node.js nehmen, bin dann aber doch bei Python für den Server geblieben, da ich da einen enormen Mehrwert für meine Arbeit sehe. Mit WebSockets setzte ich auf eine bidirektionale Kommunikation und damit auf die Push Fähigkeiten des Servers. Für den Client wollte ich deshalb meine Lieblingstechnologie aus .NET mit an Board haben: Rx

Für die Server Seite habe ich da schon etwas länger gesucht. Ich hätte ja gerne eine Extension für flask gehabt, weil sich damit REST Services schnell und einfach implementieren lassen, meine Versuche unter Windows sind aber im Sand verlaufen. Socket.IO oder …schlussendlich bin ich bei websockify (websock.js) für den Client gelandet. Warum? Weil es schon binäre Streams unterstützt, indem es auf der Client-Seite in JavaScript eine base64 Codierung vornimmt und am Server einen TCP Raw SocketProxy beinhaltet der den Datenverkehr weiterleiten kann (funktioniert nur unter linux, bzw. Python 3.0, da fromfd in früheren Versionen von Python nicht unterstütz wird). Alles in allem gute Vorraussetzungen um in einer Umgebung mit EmbeddedDevices und Industrie-Automation eingesetzt werden zu können – spricht genau mein Arbeitsumfeld. Hier also nochmal zusammengefasst der komplette Software-Stack:

    • wegen der Arbeit
    • weil es Spaß macht
    • weil es mindestens … wenn nicht noch mehr Open Source Module gibt
    • weil produktiver
    • weil lesbarer
    • und angenehmer als JavaScript
    • wer doch lieber JavaScript ließt: copy/paste nach http://js2coffee.org
  • websockify  (LINUX)
    • für den Server
    • weil es alle Protokollversionen von WebSocket unterstütz
    • Raw TCP Socket Forwarding
    • für den Server
    • Unterstützung für populäre Web-Frameworks
      • Tornado (Nonblocking)
      • CherryPy (minimalistisch)
        • unter Windows auch das einzige Framework das ich überreden konnte mit mir zu sprechen
        • da sitzt wohl der Fehler 50cm vor dem Bildschirm
      • greenlet (ähnlich node.js)
    • für den Client (Dokumentation)
    • Teil von websockify
    • nutzt web-socket-js (Fallback auf Flash … und ja ich habe mir auch socket.io angeschaut)
    • um WebSocket push noch mehr zu pushen
    • vor allem wegen Komposition und Filtern der Daten
    • zum schnellen und leichten entwickeln eigener Protokolle auf Basis von JSON

Schlussendlich könnt ihr dieses Beispiel auch mit einem anderen WebSocketServer durchführen. Ich werde zu einem späteren Zeitpunkt evlt. auch noch andere austesten.

Implementierung

Der WebSocket ist schnell programmiert. Wer lieber die native API nutzen, oder eine Einführung zu WebSockets haben will, findet über die ersten drei Treffer bei Google passenden Lesestoff.

Wir nehmen:

  1. WebSocket
  2. Rx.Subject

Damit kann ich die Daten leicht verarbeiten. Über den WebSocket lese ich mir die Daten als JSON aus. Eine spätere Entscheidung zu Gunsten eines binären Protokolls ist hier jederzeit möglich. Ein websock.js liefert ein Beispiel für telnet mit. Für den Anfang halte ich mich erst einmal an JSON-RPC. Zuerst hatte ich mit einer eigenen Struktur herumprobiert. Statt method nahm ich signal und für params stand data im Text. Also wie ich es drehe und wende, andere Namen gleicher Zweck. Über die id kann ich wie über AJAX gewohnt eine Response schicken. Zu einem JSON-RPC Server der auf JavaScript Seite läuft und durch den Client selbst initialisiert wird, fehlt hier auch nicht mehr viel. Die Erweiterung  zur Version 2.0 und  Validierung kann ich später über die Kombinatoren von Rx vornehmen. jQuery nutze ich zum parsen des JSON Strings. Später kommt noch das UI dazu. (Da mein GIST Plugin gerade de Geist aufgibt, gibts den GIST nur als Link).

https://gist.github.com/1871767

1 ws = new Websock 2 channel = new Rx.Subject() 3 4 log = (msg) -> console.log msg 5 6 ws.on 'message', () -> 7 log ">> WebSocket.onmessage" 8 packetSize = ws.rQlen() 9 packetData = ws.rQshiftStr packetSize 10 obj = $.parseJSON packetData 11 channel.onNext obj 12 13 ws.on 'open', (e) -> 14 log ">> WebSocket.onopen" 15 log "<< WebSocket.send_string" 16 ws.send_string '{"method":"scanned", "params": ["1"], "id":1}' 17 ws.send_string '{"method":"scanned", "params": ["2"], "id":2}' 18 ws.send_string '{"method":"shutter", "params": ["open"], "id":3}' 19 ws.send_string '{"method":"shutter", "params": ["close"], "id":4}' 20 ws.send_string '{"method":"scanned", "params": ["3"], "id":5}' 21 22 ws.on 'close', (e) -> 23 log ">> WebSocket.onclose" 24 channel.onCompleted '{"message":">> RX.onCompleted"}' 25 26 ws.on 'error', (e) -> 27 log ">> WebSocket.onerror" 28 channel.onError '{"error":">> RX.onError"}'

Der eigentliche Spaß kommt erst jetzt, wenn wir Rx mit einbinden. Ob ich hier noch Rx brauche, wird sich der ein oder andere fragen. Den Mehrwert sehe ich in der schlanken Syntax topic/subscribe. Zu einem späteren Zeitpunkt kann ich aus meinem Prototypen ein jQuery Plugin bauen, oder meine Daten nach belieben Transformieren und Filtern. Es sollte mir auch leicht fallen das Transportformat von JSON-RPC nach binär zu ändern, wenn ich es für nötig halte.

1 #/*select topic based on methodName*/ 2 topic = (methodName) -> 3 method = channel 4 .where((msg) -> msg.method == methodName) 5 .select((msg) -> {"params":msg.params, "id":msg.id}) 6 7 bind = (methodName, handler) -> 8 topic(methodName).subscribe(handler) 9 10 topic("shutter").subscribe (msg) -> log ("Shutter: " + msg.params[0]) 11 #/* and with jQuery like binding syntax*/ 12 bind 'scanned', (msg) -> log ("Barcode Scanned: " + msg.params[0])

 

Kommen wir zum Schluss auf den Server. Um einen einfachen Test zu erstellen wollte ich erst einmal einen Echo Endpunkt erstellen. Relativ einfach und ich denke keiner weiteren Erklärung würdig.

 

1 # -*- coding: utf-8 -*- 2 from multiprocessing import Process 3 4 def cherrypy_server(host="127.0.0.1", 5 port=54321): 6 7 import cherrypy 8 import ws4py.websocket as ws 9 import ws4py.server.cherrypyserver as server 10 11 config = {'server.socket_host': host, 12 'server.socket_port': port, 13 'log.screen': False 14 } 15 cherrypy.config.update(config) 16 server.WebSocketPlugin(cherrypy.engine).subscribe() 17 cherrypy.tools.websocket = server.WebSocketTool() 18 19 class Root(object): 20 @cherrypy.expose 21 def index(self): 22 pass 23 24 config = { 25 '/': { 26 'tools.websocket.on': True, 27 'tools.websocket.handler_cls': ws.EchoWebSocket 28 } 29 } 30 cherrypy.quickstart(Root(), '/', config) 31 32 if __name__ == '__main__': 33 p0 = Process(target=cherrypy_server) 34 p0.daemon = True 35 p0.start() 36 p0.join()

Auswertung

Kurz, knapp, ausbaufähig und kein Code für den Mülleimer. Nichts aufregendes, grundsolide. Nicht was ich mir nicht auch im Web zusammensuchen kann.Voller Technologie, die dem Entwickler die Arbeit leichter machen. Abschließend noch ein kurzer Screenshot der unspektakulären Konsolenausgabe:

rx_websocket_protokoll

Published Dienstag, 21. Februar 2012 00:47 von Rainer Schuster
Abgelegt unter: , , , ,

Kommentare

Keine Kommentare

Kommentar abgeben

(verpflichtend) 
(verpflichtend) 
(optional)
(verpflichtend)