From: Drew Fisher Date: Wed, 7 May 2014 21:15:03 +0000 (-0700) Subject: End-to-end connectivity demo X-Git-Url: http://git.zarvox.org/shortlog/%7B%7B%20url_for%28%27main.login_page%27%29%20%7D%7D?a=commitdiff_plain;h=19e5144e74d93e68924c6e7b97fadd494857ce19;p=imoo.git End-to-end connectivity demo Enough tornado and react.js to get events passing from browser to chat network and back. --- diff --git a/reactornado/dispatcher.js b/reactornado/dispatcher.js new file mode 100644 index 0000000..872ae41 --- /dev/null +++ b/reactornado/dispatcher.js @@ -0,0 +1,13 @@ +// I have no idea if this is good form or not +var Dispatcher = { + send_msg: function(destination, method, params) { + var m = { + "destination": destination, + "method": method, + "params": params + }; + window.ws_conn.send(JSON.stringify(m)); + } +}; + +window.Dispatcher = Dispatcher; diff --git a/reactornado/index.html b/reactornado/index.html new file mode 100644 index 0000000..3fa7df3 --- /dev/null +++ b/reactornado/index.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + +
+ +
+ +
+ + diff --git a/reactornado/main.js b/reactornado/main.js new file mode 100644 index 0000000..76cf334 --- /dev/null +++ b/reactornado/main.js @@ -0,0 +1,33 @@ +// this is like a main.js + +window.blist = {} + +window.onload = function () { + // Create websocket and attach to window + var ws = new WebSocket("ws://localhost:8889/ws"); + + ws.onmessage = function(evt) { + //console.log("message received: " + evt.data); + var message = JSON.parse(evt.data) + console.log(message); + }; + ws.onclose = function(evt) { + console.log("connection closed"); + }; + ws.onopen = function(evt) { + console.log("opened"); + }; + window.ws_conn = ws; + + // Attach React-rendered components + React.renderComponent( + window.widgets.LoginForm(), + document.getElementById('loginform') + ); + + window.blist = React.renderComponent( + window.widgets.BuddyList(), + document.getElementById('blist') + ); + console.log("loaded"); +}; diff --git a/reactornado/ribbon_passthrough.py b/reactornado/ribbon_passthrough.py new file mode 100644 index 0000000..8d3c110 --- /dev/null +++ b/reactornado/ribbon_passthrough.py @@ -0,0 +1,88 @@ +import json +import logging +import os +import socket +import struct +import subprocess + +import tornado.httpserver +import tornado.ioloop +import tornado.web +import tornado.websocket + +pwd = os.path.dirname(os.path.abspath(__file__)) + +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s : %(levelname)s : %(name)s : %(message)s', + ) + +log = logging.getLogger() + +class WSHandler(tornado.websocket.WebSocketHandler): + def on_ribbon_length_received(self, data): + length = struct.unpack(">i", data)[0] + log.info("ribbon sent length {}".format(length)) + self.ribbon_stream.read_bytes(length, self.on_ribbon_message) + + def on_ribbon_message(self, message): + log.info("ribbon sent data {}".format(message)) + # Dispatch it straight to the frontend; #nofilter + self.write_message(message) + # And prepare for the next message + self.ribbon_stream.read_bytes(4, self.on_ribbon_length_received) + + def on_ribbon_ready(self): + log.info("ribbon ready") + self.ribbon_ready = True + self.dispatch_backlog() + # Set up reads to trigger the length callback + self.ribbon_stream.read_bytes(4, self.on_ribbon_length_received) + + def dispatch_backlog(self): + log.info("dispatch backlog") + for message in self.backlog: + log.info("write {}".format(message)) + length = struct.pack(">I", len(message)) + self.ribbon_stream.write(length) + self.ribbon_stream.write(message) + self.backlog = [] + + def queue_message(self, message): + self.backlog.append(message) + if self.ribbon_ready: + self.dispatch_backlog() + + def open(self): + # Create a new connection to ribbon. + self.ribbon_ready = False + self.backlog = [] # A list of bytestrings queued to be sent on the socket. + self.ribbon_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + self.ribbon_stream = tornado.iostream.IOStream(self.ribbon_socket) + self.ribbon_stream.connect( ("localhost", 8888), self.on_ribbon_ready) + log.info('new connection') + self.write_message('{"key":"value"}') + def on_message(self, message): + log.info('message received: %r', message) + data = json.loads(message) + self.queue_message(message.encode("latin1")) + + def on_close(self): + log.info('connection closed') + + +application = tornado.web.Application([ + (r'/ws', WSHandler), +]) + + +if __name__ == "__main__": + # Spawn the ribbon subprocess + ribbon_binary = os.path.join(os.path.dirname(pwd), "ribbon", "ribbon") + ribbon = subprocess.Popen( [ribbon_binary], close_fds=True ) + + # Spawn the http server + http_server = tornado.httpserver.HTTPServer(application) + http_server.listen(8889) + log.info("starting up") + tornado.ioloop.IOLoop.instance().start() diff --git a/reactornado/style.css b/reactornado/style.css new file mode 100644 index 0000000..8268956 --- /dev/null +++ b/reactornado/style.css @@ -0,0 +1,46 @@ +.form { + float: left; +} + +.form > label { + display: block; +} +.form > label > input { + display: block; + margin-bottom: 20px; +} + +#blist { + float: right; +} + +/* buddy list things */ +.blistitem { + width: 200px; + height: 48px; + font-size: small; +} + +.blistitem > img { + display: inline-block; + width: 48px; + height: 48px; + float: left; +} + +.blistitemcontainer { + display: inline-block; + vertical-align: top; + width: 152px; +} + +.blist-buddyname { + font-weight: bold; +} + +.blisttext { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; +} diff --git a/reactornado/widgets.js b/reactornado/widgets.js new file mode 100644 index 0000000..be04c3d --- /dev/null +++ b/reactornado/widgets.js @@ -0,0 +1,99 @@ +/** @jsx React.DOM */ + +// Testing stuff +var List = React.createClass({ + render: function () { + var items = this.props.items; + var listitems = []; + for (var i = 0 ; i < items.length ; i++) { + listitems.push({items[i]}); + } + return ; + } +}); + +var ListItem = React.createClass({ + render: function () { + return
  • {this.props.children}
  • ; + } +}); + + +// Login form + +var LoginForm = React.createClass({ + mixins: [React.addons.LinkedStateMixin], + getInitialState: function () { + return { + "protocol": "prpl-jabber", + "username": "", + "password": "" + }; + }, + handleLogin: function () { + console.log("Imma log in now"); + command = { + "destination": "accounts", + "method": "create_account", + "params": this.state + }; + window.ws_conn.send(JSON.stringify(command)); + }, + + render: function () { + return ( +
    + + + + +
    + ); + } +}); + + +// Buddy list stuff + +var BuddyListItem = React.createClass({ + render: function () { + return
    + +
    +
    {this.props.display_name}
    +
    {this.props.status}
    +
    +
    ; + } +}); + +var BuddyList = React.createClass({ + getInitialState: function () { + return {"buddies": []}; + }, + onChange: function(newData) { + this.setState( { "buddies": newData } ); + }, + render: function () { + // for each thing in this.state: + // render thing + var things = []; + for (var i = 0 ; i < this.state.buddies.length ; i++) { + var s = this.state.buddies[i]; + things.push(BuddyListItem(this.state.buddies[i])); + } + return
    { things }
    ; + } +}); + +// integration for now: +window.widgets = { + "List": List, + "ListItem": ListItem, + "BuddyListItem": BuddyListItem, + "LoginForm": LoginForm, + "BuddyList": BuddyList +};