-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Evan Wallace
committed
Mar 3, 2011
0 parents
commit 0a04c1f
Showing
5 changed files
with
486 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.DS_Store | ||
*.pyc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# A socket.io bridge for Python | ||
|
||
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. | ||
|
||
## Installation | ||
|
||
This bridge requires [node.js](http://nodejs.org) and [socket.io](http://socket.io). Installation instructions can be found on their respective websites. | ||
|
||
## Usage | ||
|
||
This bridge is designed to be self-contained, so `io.py` is the only file you need. A server is created by subclassing `io.Socket` and overriding the `on_connect`, `on_message`, and/or `on_disconnect` methods: | ||
|
||
import io | ||
|
||
class Server(io.Socket): | ||
def on_connect(self, client): | ||
print client, 'connected' | ||
self.broadcast(str(client) + ' connected') | ||
print 'there are now', len(self.clients), 'clients' | ||
def on_message(self, client, message): | ||
print client, 'sent', message | ||
client.send(message) | ||
def on_disconnect(self, client): | ||
print client, 'disconnected' | ||
self.broadcast(str(client) + ' disconnected') | ||
print 'there are now', len(self.clients), 'clients' | ||
|
||
Server().listen(5000) | ||
|
||
The client in the browser just uses the same interface that regular socket.io clients use: | ||
|
||
<script type="text/javascript" src="http://localhost:5000/socket.io/socket.io.js"></script> | ||
<script type="text/javascript"> | ||
|
||
function log(html) { | ||
document.body.innerHTML += html + '<br>'; | ||
} | ||
|
||
var socket = new io.Socket('localhost', { port: 5000 }); | ||
socket.on('connect', function() { | ||
log('connect'); | ||
}); | ||
socket.on('message', function(data) { | ||
log('message: ' + data); | ||
}); | ||
socket.on('disconnect', function() { | ||
log('disconnect'); | ||
}); | ||
socket.connect(); | ||
|
||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
''' | ||
A socket.io bridge for Python | ||
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. | ||
For the latest source, visit https://github.com/evanw/socket.io-python | ||
''' | ||
|
||
import os | ||
import json | ||
import atexit | ||
import socket | ||
import tempfile | ||
import threading | ||
import subprocess | ||
|
||
_js = ''' | ||
var http = require('http'); | ||
var dgram = require('dgram'); | ||
var io = require('socket.io'); | ||
// http server | ||
var server = http.createServer(function() {}); | ||
server.listen(%d); | ||
// udp connection to server | ||
var udp = dgram.createSocket('udp4'); | ||
udp.on('message', function(data) { | ||
var json = JSON.parse(data.toString()); | ||
if (json.broadcast) { | ||
for (var session in socket.clients) { | ||
socket.clients[session].send(json.data); | ||
} | ||
} else if (json.session in socket.clients) { | ||
socket.clients[json.session].send(json.data); | ||
} | ||
}); | ||
udp.bind(%d, 'localhost'); | ||
// socket.io connection to clients | ||
var socket = io.listen(server); | ||
function send(client, command, data) { | ||
data = JSON.stringify({ | ||
session: client.sessionId, | ||
command: command, | ||
data: data, | ||
address: client.connection.remoteAddress, | ||
port: client.connection.remotePort | ||
}); | ||
udp.send(new Buffer(data), 0, data.length, %d, 'localhost'); | ||
} | ||
socket.on('connection', function(client) { | ||
send(client, 'connect', null); | ||
client.on('message', function(data) { | ||
send(client, 'message', data); | ||
}); | ||
client.on('disconnect', function() { | ||
send(client, 'disconnect', null); | ||
}); | ||
}); | ||
''' | ||
|
||
class Client: | ||
''' | ||
Represents a client connection. Each client has these properties: | ||
server - the Socket instance that owns this client | ||
session - the session id used by node (a string of numbers) | ||
address - the remote address of the client | ||
port - the remote port of the client | ||
''' | ||
|
||
def __init__(self, server, session, address, port): | ||
self.server = server | ||
self.session = session | ||
self.address = address | ||
self.port = port | ||
|
||
def send(self, data): | ||
''' | ||
Send a message to this client. | ||
data - a string with the data to transmit | ||
''' | ||
self.server._send(data, { 'session': self.session }) | ||
|
||
def __str__(self): | ||
''' | ||
Returns "client-ADDRESS:PORT", where ADDRESS and PORT are the | ||
remote address and port of the client. | ||
''' | ||
return 'client-%s:%s' % (self.address, self.port) | ||
|
||
class Socket: | ||
''' | ||
This is a socket.io server, and is meant to be subclassed. A subclass | ||
might look like this: | ||
import io | ||
class Server(io.Socket): | ||
def on_connect(self, client): | ||
print client, 'connected' | ||
self.broadcast(str(client) + ' connected') | ||
print 'there are now', len(self.clients), 'clients' | ||
def on_message(self, client, message): | ||
print client, 'sent', message | ||
client.send(message) | ||
def on_disconnect(self, client): | ||
print client, 'disconnected' | ||
self.broadcast(str(client) + ' disconnected') | ||
print 'there are now', len(self.clients), 'clients' | ||
Server().listen(5000) | ||
The server has self.clients, a dictionary of client session ids to | ||
Client instances. | ||
''' | ||
|
||
def __init__(self): | ||
self.clients = {} | ||
|
||
def _handle(self, info): | ||
command = info['command'] | ||
session = info['session'] | ||
if command == 'connect': | ||
self.clients[session] = Client(self, session, info['address'], info['port']) | ||
self.on_connect(self.clients[session]) | ||
elif command == 'message': | ||
if session in self.clients: | ||
self.on_message(self.clients[session], info['data']) | ||
elif command == 'disconnect': | ||
if session in self.clients: | ||
client = self.clients[session] | ||
del self.clients[session] | ||
self.on_disconnect(client) | ||
|
||
def on_connect(self, client): | ||
''' | ||
Called after a client connects. Override this in a subclass to | ||
be notified of connections. This will be called on a separate | ||
thread. | ||
client - a Client instance representing the connection | ||
''' | ||
pass | ||
|
||
def on_message(self, client, data): | ||
''' | ||
Called when client sends a message. Override this in a subclass to | ||
be notified of sent messages. This will be called on a separate | ||
thread. | ||
client - a Client instance representing the connection | ||
data - a string with the transmitted data | ||
''' | ||
pass | ||
|
||
def on_disconnect(self, client): | ||
''' | ||
Called after a client disconnects. Override this in a subclass to | ||
be notified of disconnections. This will be called on a separate | ||
thread. | ||
client - a Client instance representing the connection | ||
''' | ||
pass | ||
|
||
def broadcast(self, data): | ||
''' | ||
Send a message to all connected clients. | ||
data - a string with the data to transmit | ||
''' | ||
self._send(data, { 'broadcast': True }) | ||
|
||
def listen(self, ws_port, js_port=None, py_port=None): | ||
''' | ||
Start the server thread on the port given by ws_port. We actually | ||
need three ports: one for the browser, one for node.js, and one for | ||
this module: | ||
browser: node.js: this module: | ||
io.Socket <-> ws_port <-> js_port <-> py_port <-> io.Socket | ||
ws_port - the port that the browser will connect to | ||
js_port - the port that node.js will use to talk to python | ||
defaults to ws_port + 1 | ||
py_port - the port that python will use to talk to node.js | ||
defaults to ws_port + 2 | ||
''' | ||
|
||
# set default ports | ||
if js_port is None: js_port = ws_port + 1 | ||
if py_port is None: py_port = ws_port + 2 | ||
|
||
# create a custom node.js script | ||
js = _js % (ws_port, js_port, py_port) | ||
handle, path = tempfile.mkstemp(suffix='.js') | ||
os.write(handle, js) | ||
os.close(handle) | ||
|
||
# run that script in node.js | ||
process = subprocess.Popen(['node', path]) | ||
def cleanup(): | ||
process.kill() | ||
os.remove(path) | ||
atexit.register(cleanup) | ||
|
||
# make sure we can communicate with node.js | ||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
sock.bind(('localhost', py_port)) | ||
def send(data, info): | ||
info['data'] = data | ||
sock.sendto(json.dumps(info), ('localhost', js_port)) | ||
self._send = send | ||
|
||
# run the server | ||
while 1: | ||
self._handle(json.loads(sock.recvfrom(4096)[0])) |
Oops, something went wrong.