├── .gitignore ├── package.json ├── tests.py ├── README.md ├── tests.html └── socket_io.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket.io-python", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "socket.io": "0.6" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | from SimpleHTTPServer import SimpleHTTPRequestHandler 2 | from SocketServer import TCPServer 3 | import socket_io as io 4 | 5 | class Server(io.Server): 6 | def on_connect(self, client): 7 | client.name = None 8 | 9 | def on_message(self, client, message): 10 | command, value = message.split(':', 1) 11 | print 'got message', command, 'starting with', '"%s"' % value[:30] 12 | 13 | if command == 'setname': 14 | client.name = value 15 | elif command == 'broadcast': 16 | self.broadcast(value) 17 | elif command == 'echo': 18 | client.send(value) 19 | elif command == 'send': 20 | name, data = value.split(':', 1) 21 | for client in self.clients.values(): 22 | if client.name == name: 23 | client.send(data) 24 | 25 | def on_disconnect(self, client): 26 | pass 27 | 28 | def run_server(): 29 | from SimpleHTTPServer import SimpleHTTPRequestHandler 30 | from SocketServer import TCPServer 31 | class NiceServer(TCPServer): 32 | allow_reuse_address = True 33 | NiceServer(('', 8000), SimpleHTTPRequestHandler).serve_forever() 34 | 35 | import threading 36 | t = threading.Thread(target=run_server) 37 | t.daemon = True 38 | t.start() 39 | 40 | print '\nopen http://localhost:8000/tests.html to run tests\n' 41 | Server().listen(5000) 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A socket.io bridge for Python 2 | 3 | This gives Python users access to socket.io, a node.js library. This library provides simple and efficient bidirectional communication between browsers and servers over a message-oriented socket. Transport is normalized over various technologies including WebSockets, Flash sockets, and AJAX polling. 4 | 5 | ## Installation 6 | 7 | This bridge requires [node.js](http://nodejs.org) and [socket.io](http://socket.io). Install node.js and [npm](http://npmjs.org/), then run `npm install .` in this directory to install the correct version of socket.io (the newer versions have changed their API). 8 | 9 | ## Usage 10 | 11 | This bridge is designed to be self-contained, so `socket_io.py` is the only file you need. A server is created by subclassing `socket_io.Socket` and overriding the `on_connect`, `on_message`, and/or `on_disconnect` methods: 12 | 13 | import socket_io as io 14 | 15 | class Server(io.Server): 16 | def on_connect(self, client): 17 | print client, 'connected' 18 | self.broadcast(str(client) + ' connected') 19 | print 'there are now', len(self.clients), 'clients' 20 | 21 | def on_message(self, client, message): 22 | print client, 'sent', message 23 | client.send(message) 24 | 25 | def on_disconnect(self, client): 26 | print client, 'disconnected' 27 | self.broadcast(str(client) + ' disconnected') 28 | print 'there are now', len(self.clients), 'clients' 29 | 30 | Server().listen(5000) 31 | 32 | The client in the browser just uses the same interface that regular socket.io clients use: 33 | 34 | 35 | 54 | -------------------------------------------------------------------------------- /tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 203 | 204 |

Tests

205 |

If the tests below don't work, try running tests.py and refreshing the page.

206 | 207 | -------------------------------------------------------------------------------- /socket_io.py: -------------------------------------------------------------------------------- 1 | ''' 2 | A socket.io bridge for Python 3 | 4 | This gives Python users access to socket.io, a node.js library. This library 5 | provides simple and efficient bidirectional communication between browsers 6 | and servers over a message-oriented socket. Transport is normalized over 7 | various technologies including WebSockets, Flash sockets, and AJAX polling. 8 | 9 | For the latest source, visit https://github.com/evanw/socket.io-python 10 | ''' 11 | 12 | import os 13 | import json 14 | import atexit 15 | import socket 16 | import tempfile 17 | import subprocess 18 | 19 | # A socket.io template that connects to a Python socket over TCP and forwards 20 | # messages as JSON strings separated by null characters. TCP was chosen over 21 | # UDP to allow for arbitrarily large packets. 22 | _js = ''' 23 | var net = require('net'); 24 | var http = require('http'); 25 | var io = require('socket.io'); 26 | 27 | // http server 28 | var server = http.createServer(function() {}); 29 | server.listen(%d); 30 | 31 | // tcp connection to server 32 | var tcp = net.createConnection(%d, 'localhost'); 33 | var buffer = ''; 34 | tcp.addListener('data', function(data) { 35 | var i = 0; 36 | while (i < data.length) { 37 | if (data[i] == 0) { 38 | sendToClient(JSON.parse(buffer + data.toString('utf8', 0, i))); 39 | data = data.slice(i + 1); 40 | buffer = ''; 41 | i = 0; 42 | } else { 43 | i++; 44 | } 45 | } 46 | buffer += data.toString('utf8'); 47 | }); 48 | 49 | // socket.io connection to clients 50 | var socket = io.listen(server); 51 | function sendToServer(client, command, data) { 52 | data = JSON.stringify({ 53 | session: client.sessionId, 54 | command: command, 55 | data: data, 56 | address: client.connection.remoteAddress, 57 | port: client.connection.remotePort 58 | }); 59 | tcp.write(data + '\0'); 60 | } 61 | function sendToClient(json) { 62 | if (json.broadcast) { 63 | for (var session in socket.clients) { 64 | socket.clients[session].send(json.data); 65 | } 66 | } else if (json.session in socket.clients) { 67 | socket.clients[json.session].send(json.data); 68 | } 69 | } 70 | socket.on('connection', function(client) { 71 | sendToServer(client, 'connect', null); 72 | client.on('message', function(data) { 73 | sendToServer(client, 'message', data); 74 | }); 75 | client.on('disconnect', function() { 76 | sendToServer(client, 'disconnect', null); 77 | }); 78 | }); 79 | ''' 80 | 81 | class Client: 82 | ''' 83 | Represents a client connection. Each client has these properties: 84 | 85 | server - the Socket instance that owns this client 86 | session - the session id used by node (a string of numbers) 87 | address - the remote address of the client 88 | port - the remote port of the client 89 | ''' 90 | 91 | def __init__(self, server, session, address, port): 92 | self.server = server 93 | self.session = session 94 | self.address = address 95 | self.port = port 96 | 97 | def send(self, data): 98 | ''' 99 | Send a message to this client. 100 | 101 | data - a string with the data to transmit 102 | ''' 103 | self.server._send(data, { 'session': self.session }) 104 | 105 | def __str__(self): 106 | ''' 107 | Returns "client-ADDRESS:PORT", where ADDRESS and PORT are the 108 | remote address and port of the client. 109 | ''' 110 | return 'client-%s:%s' % (self.address, self.port) 111 | 112 | class Server: 113 | ''' 114 | This is a socket.io server, and is meant to be subclassed. A subclass 115 | might look like this: 116 | 117 | import socket_io as io 118 | 119 | class Server(io.Server): 120 | def on_connect(self, client): 121 | print client, 'connected' 122 | self.broadcast(str(client) + ' connected') 123 | print 'there are now', len(self.clients), 'clients' 124 | 125 | def on_message(self, client, message): 126 | print client, 'sent', message 127 | client.send(message) 128 | 129 | def on_disconnect(self, client): 130 | print client, 'disconnected' 131 | self.broadcast(str(client) + ' disconnected') 132 | print 'there are now', len(self.clients), 'clients' 133 | 134 | Server().listen(5000) 135 | 136 | The server has self.clients, a dictionary of client session ids to 137 | Client instances. 138 | ''' 139 | 140 | def __init__(self): 141 | self.clients = {} 142 | 143 | def _handle(self, info): 144 | command = info['command'] 145 | session = info['session'] 146 | if command == 'connect': 147 | self.clients[session] = Client(self, session, info['address'], info['port']) 148 | self.on_connect(self.clients[session]) 149 | elif command == 'message': 150 | if session in self.clients: 151 | self.on_message(self.clients[session], info['data']) 152 | elif command == 'disconnect': 153 | if session in self.clients: 154 | client = self.clients[session] 155 | del self.clients[session] 156 | self.on_disconnect(client) 157 | 158 | def on_connect(self, client): 159 | ''' 160 | Called after a client connects. Override this in a subclass to 161 | be notified of connections. 162 | 163 | client - a Client instance representing the connection 164 | ''' 165 | pass 166 | 167 | def on_message(self, client, data): 168 | ''' 169 | Called when client sends a message. Override this in a subclass to 170 | be notified of sent messages. 171 | 172 | client - a Client instance representing the connection 173 | data - a string with the transmitted data 174 | ''' 175 | pass 176 | 177 | def on_disconnect(self, client): 178 | ''' 179 | Called after a client disconnects. Override this in a subclass to 180 | be notified of disconnections. 181 | 182 | client - a Client instance representing the connection 183 | ''' 184 | pass 185 | 186 | def broadcast(self, data): 187 | ''' 188 | Send a message to all connected clients. 189 | 190 | data - a string with the data to transmit 191 | ''' 192 | self._send(data, { 'broadcast': True }) 193 | 194 | def listen(self, ws_port, py_port=None): 195 | ''' 196 | Run the server on the port given by ws_port. We actually need two 197 | ports, an external one for the browser (ws_port) and an internal 198 | one to communicate with node.js (py_port): 199 | 200 | browser: node.js: this module: 201 | --------- ---------------------- --------------------- 202 | io.Socket <-> ws_port <-> TCP socket <-> py_port <-> io.Socket 203 | 204 | ws_port - the port that the browser will connect to 205 | py_port - the port that python will use to talk to node.js 206 | (defaults to ws_port + 1) 207 | ''' 208 | 209 | # set default port 210 | if py_port is None: 211 | py_port = ws_port + 1 212 | 213 | # create a custom node.js script 214 | js = _js % (ws_port, py_port) 215 | handle, path = tempfile.mkstemp(suffix='.js') 216 | os.write(handle, js) 217 | os.close(handle) 218 | 219 | # run that script in node.js 220 | process = subprocess.Popen(['node', path]) 221 | def cleanup(): 222 | process.kill() 223 | os.remove(path) 224 | atexit.register(cleanup) 225 | 226 | # make sure we can communicate with node.js 227 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 228 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 229 | sock.bind(('localhost', py_port)) 230 | sock.listen(0) 231 | sock, addr = sock.accept() 232 | def send(data, info): 233 | info['data'] = data 234 | sock.send(json.dumps(info) + '\0') 235 | self._send = send 236 | 237 | # run the server 238 | buffer = '' 239 | while 1: 240 | buffer += sock.recv(4096) 241 | index = buffer.find('\0') 242 | while index >= 0: 243 | data, buffer = buffer[0:index], buffer[index+1:] 244 | self._handle(json.loads(data)) 245 | index = buffer.find('\0') 246 | --------------------------------------------------------------------------------