├── .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 |
--------------------------------------------------------------------------------