Revision e6daa90e share/websockify/websocket.py
share/websockify/websocket.py | ||
---|---|---|
6 | 6 |
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) |
7 | 7 |
|
8 | 8 |
Supports following protocol versions: |
9 |
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 |
|
10 |
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 |
|
9 |
- http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07 |
|
11 | 10 |
- http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 |
11 |
- http://tools.ietf.org/html/rfc6455 |
|
12 | 12 |
|
13 | 13 |
You can make a cert/key with openssl using: |
14 | 14 |
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem |
... | ... | |
16 | 16 |
|
17 | 17 |
''' |
18 | 18 |
|
19 |
import os, sys, time, errno, signal, socket, traceback, select
|
|
19 |
import os, sys, time, errno, signal, socket, select, logging
|
|
20 | 20 |
import array, struct |
21 | 21 |
from base64 import b64encode, b64decode |
22 | 22 |
|
... | ... | |
37 | 37 |
except: from SimpleHTTPServer import SimpleHTTPRequestHandler |
38 | 38 |
|
39 | 39 |
# python 2.6 differences |
40 |
try: from hashlib import md5, sha1
|
|
41 |
except: from md5 import md5; from sha import sha as sha1
|
|
40 |
try: from hashlib import sha1 |
|
41 |
except: from sha import sha as sha1 |
|
42 | 42 |
|
43 | 43 |
# python 2.5 differences |
44 | 44 |
try: |
... | ... | |
50 | 50 |
return struct.unpack(fmt, slice) |
51 | 51 |
|
52 | 52 |
# Degraded functionality if these imports are missing |
53 |
for mod, sup in [('numpy', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'), |
|
54 |
('multiprocessing', 'Multi-Processing'), |
|
55 |
('resource', 'daemonizing')]: |
|
53 |
for mod, msg in [('numpy', 'HyBi protocol will be slower'), |
|
54 |
('ssl', 'TLS/SSL/wss is disabled'), |
|
55 |
('multiprocessing', 'Multi-Processing is disabled'), |
|
56 |
('resource', 'daemonizing is disabled')]: |
|
56 | 57 |
try: |
57 | 58 |
globals()[mod] = __import__(mod) |
58 | 59 |
except ImportError: |
59 | 60 |
globals()[mod] = None |
60 |
print("WARNING: no '%s' module, %s is slower or disabled" % (
|
|
61 |
mod, sup)) |
|
61 |
print("WARNING: no '%s' module, %s" % (mod, msg))
|
|
62 |
|
|
62 | 63 |
if multiprocessing and sys.platform == 'win32': |
63 | 64 |
# make sockets pickle-able/inheritable |
64 | 65 |
import multiprocessing.reduction |
65 | 66 |
|
66 | 67 |
|
67 |
class WebSocketServer(object): |
|
68 |
# HTTP handler with WebSocket upgrade support |
|
69 |
class WebSocketRequestHandler(SimpleHTTPRequestHandler): |
|
68 | 70 |
""" |
69 |
WebSockets server class. |
|
70 |
Must be sub-classed with new_client method definition. |
|
71 |
WebSocket Request Handler Class, derived from SimpleHTTPRequestHandler. |
|
72 |
Must be sub-classed with new_websocket_client method definition. |
|
73 |
The request handler can be configured by setting optional |
|
74 |
attributes on the server object: |
|
75 |
|
|
76 |
* only_upgrade: If true, SimpleHTTPRequestHandler will not be enabled, |
|
77 |
only websocket is allowed. |
|
78 |
* verbose: If true, verbose logging is activated. |
|
79 |
* daemon: Running as daemon, do not write to console etc |
|
80 |
* record: Record raw frame data as JavaScript array into specified filename |
|
81 |
* run_once: Handle a single request |
|
82 |
* handler_id: A sequence number for this connection, appended to record filename |
|
71 | 83 |
""" |
72 |
|
|
73 | 84 |
buffer_size = 65536 |
74 | 85 |
|
75 |
|
|
76 |
server_handshake_hixie = """HTTP/1.1 101 Web Socket Protocol Handshake\r |
|
77 |
Upgrade: WebSocket\r |
|
78 |
Connection: Upgrade\r |
|
79 |
%sWebSocket-Origin: %s\r |
|
80 |
%sWebSocket-Location: %s://%s%s\r |
|
81 |
""" |
|
82 |
|
|
83 |
server_handshake_hybi = """HTTP/1.1 101 Switching Protocols\r |
|
84 |
Upgrade: websocket\r |
|
85 |
Connection: Upgrade\r |
|
86 |
Sec-WebSocket-Accept: %s\r |
|
87 |
""" |
|
88 |
|
|
89 | 86 |
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" |
90 | 87 |
|
91 |
policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
|
|
88 |
server_version = "WebSockify"
|
|
92 | 89 |
|
93 |
# An exception before the WebSocket connection was established |
|
94 |
class EClose(Exception): |
|
95 |
pass |
|
90 |
protocol_version = "HTTP/1.1" |
|
96 | 91 |
|
97 | 92 |
# An exception while the WebSocket client was connected |
98 | 93 |
class CClose(Exception): |
99 | 94 |
pass |
100 | 95 |
|
101 |
def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False, |
|
102 |
verbose=False, cert='', key='', ssl_only=None, |
|
103 |
daemon=False, record='', web='', |
|
104 |
run_once=False, timeout=0, idle_timeout=0): |
|
105 |
|
|
106 |
# settings |
|
107 |
self.verbose = verbose |
|
108 |
self.listen_host = listen_host |
|
109 |
self.listen_port = listen_port |
|
110 |
self.prefer_ipv6 = source_is_ipv6 |
|
111 |
self.ssl_only = ssl_only |
|
112 |
self.daemon = daemon |
|
113 |
self.run_once = run_once |
|
114 |
self.timeout = timeout |
|
115 |
self.idle_timeout = idle_timeout |
|
116 |
|
|
117 |
self.launch_time = time.time() |
|
118 |
self.ws_connection = False |
|
119 |
self.handler_id = 1 |
|
120 |
|
|
121 |
# Make paths settings absolute |
|
122 |
self.cert = os.path.abspath(cert) |
|
123 |
self.key = self.web = self.record = '' |
|
124 |
if key: |
|
125 |
self.key = os.path.abspath(key) |
|
126 |
if web: |
|
127 |
self.web = os.path.abspath(web) |
|
128 |
if record: |
|
129 |
self.record = os.path.abspath(record) |
|
130 |
|
|
131 |
if self.web: |
|
132 |
os.chdir(self.web) |
|
133 |
|
|
134 |
# Sanity checks |
|
135 |
if not ssl and self.ssl_only: |
|
136 |
raise Exception("No 'ssl' module and SSL-only specified") |
|
137 |
if self.daemon and not resource: |
|
138 |
raise Exception("Module 'resource' required to daemonize") |
|
139 |
|
|
140 |
# Show configuration |
|
141 |
print("WebSocket server settings:") |
|
142 |
print(" - Listen on %s:%s" % ( |
|
143 |
self.listen_host, self.listen_port)) |
|
144 |
print(" - Flash security policy server") |
|
145 |
if self.web: |
|
146 |
print(" - Web server. Web root: %s" % self.web) |
|
147 |
if ssl: |
|
148 |
if os.path.exists(self.cert): |
|
149 |
print(" - SSL/TLS support") |
|
150 |
if self.ssl_only: |
|
151 |
print(" - Deny non-SSL/TLS connections") |
|
152 |
else: |
|
153 |
print(" - No SSL/TLS support (no cert file)") |
|
154 |
else: |
|
155 |
print(" - No SSL/TLS support (no 'ssl' module)") |
|
156 |
if self.daemon: |
|
157 |
print(" - Backgrounding (daemon)") |
|
158 |
if self.record: |
|
159 |
print(" - Recording to '%s.*'" % self.record) |
|
160 |
|
|
161 |
# |
|
162 |
# WebSocketServer static methods |
|
163 |
# |
|
164 |
|
|
165 |
@staticmethod |
|
166 |
def socket(host, port=None, connect=False, prefer_ipv6=False, unix_socket=None, use_ssl=False): |
|
167 |
""" Resolve a host (and optional port) to an IPv4 or IPv6 |
|
168 |
address. Create a socket. Bind to it if listen is set, |
|
169 |
otherwise connect to it. Return the socket. |
|
170 |
""" |
|
171 |
flags = 0 |
|
172 |
if host == '': |
|
173 |
host = None |
|
174 |
if connect and not (port or unix_socket): |
|
175 |
raise Exception("Connect mode requires a port") |
|
176 |
if use_ssl and not ssl: |
|
177 |
raise Exception("SSL socket requested but Python SSL module not loaded."); |
|
178 |
if not connect and use_ssl: |
|
179 |
raise Exception("SSL only supported in connect mode (for now)") |
|
180 |
if not connect: |
|
181 |
flags = flags | socket.AI_PASSIVE |
|
182 |
|
|
183 |
if not unix_socket: |
|
184 |
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, |
|
185 |
socket.IPPROTO_TCP, flags) |
|
186 |
if not addrs: |
|
187 |
raise Exception("Could not resolve host '%s'" % host) |
|
188 |
addrs.sort(key=lambda x: x[0]) |
|
189 |
if prefer_ipv6: |
|
190 |
addrs.reverse() |
|
191 |
sock = socket.socket(addrs[0][0], addrs[0][1]) |
|
192 |
if connect: |
|
193 |
sock.connect(addrs[0][4]) |
|
194 |
if use_ssl: |
|
195 |
sock = ssl.wrap_socket(sock) |
|
196 |
else: |
|
197 |
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
|
198 |
sock.bind(addrs[0][4]) |
|
199 |
sock.listen(100) |
|
200 |
else: |
|
201 |
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) |
|
202 |
sock.connect(unix_socket) |
|
203 |
|
|
204 |
return sock |
|
205 |
|
|
206 |
@staticmethod |
|
207 |
def daemonize(keepfd=None, chdir='/'): |
|
208 |
os.umask(0) |
|
209 |
if chdir: |
|
210 |
os.chdir(chdir) |
|
211 |
else: |
|
212 |
os.chdir('/') |
|
213 |
os.setgid(os.getgid()) # relinquish elevations |
|
214 |
os.setuid(os.getuid()) # relinquish elevations |
|
215 |
|
|
216 |
# Double fork to daemonize |
|
217 |
if os.fork() > 0: os._exit(0) # Parent exits |
|
218 |
os.setsid() # Obtain new process group |
|
219 |
if os.fork() > 0: os._exit(0) # Parent exits |
|
220 |
|
|
221 |
# Signal handling |
|
222 |
def terminate(a,b): os._exit(0) |
|
223 |
signal.signal(signal.SIGTERM, terminate) |
|
224 |
signal.signal(signal.SIGINT, signal.SIG_IGN) |
|
96 |
def __init__(self, req, addr, server): |
|
97 |
# Retrieve a few configuration variables from the server |
|
98 |
self.only_upgrade = getattr(server, "only_upgrade", False) |
|
99 |
self.verbose = getattr(server, "verbose", False) |
|
100 |
self.daemon = getattr(server, "daemon", False) |
|
101 |
self.record = getattr(server, "record", False) |
|
102 |
self.run_once = getattr(server, "run_once", False) |
|
103 |
self.rec = None |
|
104 |
self.handler_id = getattr(server, "handler_id", False) |
|
105 |
self.file_only = getattr(server, "file_only", False) |
|
106 |
self.traffic = getattr(server, "traffic", False) |
|
225 | 107 |
|
226 |
# Close open files |
|
227 |
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] |
|
228 |
if maxfd == resource.RLIM_INFINITY: maxfd = 256 |
|
229 |
for fd in reversed(range(maxfd)): |
|
230 |
try: |
|
231 |
if fd != keepfd: |
|
232 |
os.close(fd) |
|
233 |
except OSError: |
|
234 |
_, exc, _ = sys.exc_info() |
|
235 |
if exc.errno != errno.EBADF: raise |
|
108 |
self.logger = getattr(server, "logger", None) |
|
109 |
if self.logger is None: |
|
110 |
self.logger = WebSocketServer.get_logger() |
|
236 | 111 |
|
237 |
# Redirect I/O to /dev/null |
|
238 |
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno()) |
|
239 |
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno()) |
|
240 |
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno()) |
|
112 |
SimpleHTTPRequestHandler.__init__(self, req, addr, server) |
|
241 | 113 |
|
242 | 114 |
@staticmethod |
243 | 115 |
def unmask(buf, hlen, plen): |
... | ... | |
254 | 126 |
b = numpy.bitwise_xor(data, mask).tostring() |
255 | 127 |
|
256 | 128 |
if plen % 4: |
257 |
#print("Partial unmask")
|
|
129 |
#self.msg("Partial unmask")
|
|
258 | 130 |
mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'), |
259 | 131 |
offset=hlen, count=(plen % 4)) |
260 | 132 |
data = numpy.frombuffer(buf, dtype=numpy.dtype('B'), |
... | ... | |
295 | 167 |
elif payload_len >= 65536: |
296 | 168 |
header = pack('>BBQ', b1, 127, payload_len) |
297 | 169 |
|
298 |
#print("Encoded: %s" % repr(header + buf))
|
|
170 |
#self.msg("Encoded: %s", repr(header + buf))
|
|
299 | 171 |
|
300 | 172 |
return header + buf, len(header), 0 |
301 | 173 |
|
302 | 174 |
@staticmethod |
303 |
def decode_hybi(buf, base64=False): |
|
175 |
def decode_hybi(buf, base64=False, logger=None):
|
|
304 | 176 |
""" Decode HyBi style WebSocket packets. |
305 | 177 |
Returns: |
306 | 178 |
{'fin' : 0_or_1, |
... | ... | |
324 | 196 |
'close_code' : 1000, |
325 | 197 |
'close_reason' : ''} |
326 | 198 |
|
199 |
if logger is None: |
|
200 |
logger = WebSocketServer.get_logger() |
|
201 |
|
|
327 | 202 |
blen = len(buf) |
328 | 203 |
f['left'] = blen |
329 | 204 |
|
... | ... | |
359 | 234 |
# Process 1 frame |
360 | 235 |
if f['masked']: |
361 | 236 |
# unmask payload |
362 |
f['payload'] = WebSocketServer.unmask(buf, f['hlen'],
|
|
237 |
f['payload'] = WebSocketRequestHandler.unmask(buf, f['hlen'],
|
|
363 | 238 |
f['length']) |
364 | 239 |
else: |
365 |
print("Unmasked frame: %s" % repr(buf))
|
|
240 |
logger.debug("Unmasked frame: %s" % repr(buf))
|
|
366 | 241 |
f['payload'] = buf[(f['hlen'] + f['masked'] * 4):full_len] |
367 | 242 |
|
368 | 243 |
if base64 and f['opcode'] in [1, 2]: |
369 | 244 |
try: |
370 | 245 |
f['payload'] = b64decode(f['payload']) |
371 | 246 |
except: |
372 |
print("Exception while b64decoding buffer: %s" %
|
|
373 |
repr(buf))
|
|
247 |
logger.exception("Exception while b64decoding buffer: %s" %
|
|
248 |
(repr(buf)))
|
|
374 | 249 |
raise |
375 | 250 |
|
376 | 251 |
if f['opcode'] == 0x08: |
... | ... | |
381 | 256 |
|
382 | 257 |
return f |
383 | 258 |
|
384 |
@staticmethod |
|
385 |
def encode_hixie(buf): |
|
386 |
return s2b("\x00" + b2s(b64encode(buf)) + "\xff"), 1, 1 |
|
387 |
|
|
388 |
@staticmethod |
|
389 |
def decode_hixie(buf): |
|
390 |
end = buf.find(s2b('\xff')) |
|
391 |
return {'payload': b64decode(buf[1:end]), |
|
392 |
'hlen': 1, |
|
393 |
'masked': False, |
|
394 |
'length': end - 1, |
|
395 |
'left': len(buf) - (end + 1)} |
|
396 |
|
|
397 |
|
|
398 |
@staticmethod |
|
399 |
def gen_md5(keys): |
|
400 |
""" Generate hash value for WebSockets hixie-76. """ |
|
401 |
key1 = keys['Sec-WebSocket-Key1'] |
|
402 |
key2 = keys['Sec-WebSocket-Key2'] |
|
403 |
key3 = keys['key3'] |
|
404 |
spaces1 = key1.count(" ") |
|
405 |
spaces2 = key2.count(" ") |
|
406 |
num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1 |
|
407 |
num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2 |
|
408 |
|
|
409 |
return b2s(md5(pack('>II8s', |
|
410 |
int(num1), int(num2), key3)).digest()) |
|
411 | 259 |
|
412 | 260 |
# |
413 |
# WebSocketServer logging/output functions
|
|
261 |
# WebSocketRequestHandler logging/output functions
|
|
414 | 262 |
# |
415 | 263 |
|
416 |
def traffic(self, token="."): |
|
417 |
""" Show traffic flow in verbose mode. """
|
|
418 |
if self.verbose and not self.daemon:
|
|
264 |
def print_traffic(self, token="."):
|
|
265 |
""" Show traffic flow mode. """ |
|
266 |
if self.traffic:
|
|
419 | 267 |
sys.stdout.write(token) |
420 | 268 |
sys.stdout.flush() |
421 | 269 |
|
422 |
def msg(self, msg): |
|
270 |
def msg(self, msg, *args, **kwargs):
|
|
423 | 271 |
""" Output message with handler_id prefix. """ |
424 |
if not self.daemon:
|
|
425 |
print("% 3d: %s" % (self.handler_id, msg))
|
|
272 |
prefix = "% 3d: " % self.handler_id
|
|
273 |
self.logger.log(logging.INFO, "%s%s" % (prefix, msg), *args, **kwargs)
|
|
426 | 274 |
|
427 |
def vmsg(self, msg): |
|
428 |
""" Same as msg() but only if verbose. """ |
|
429 |
if self.verbose: |
|
430 |
self.msg(msg) |
|
275 |
def vmsg(self, msg, *args, **kwargs): |
|
276 |
""" Same as msg() but as debug. """ |
|
277 |
prefix = "% 3d: " % self.handler_id |
|
278 |
self.logger.log(logging.DEBUG, "%s%s" % (prefix, msg), *args, **kwargs) |
|
279 |
|
|
280 |
def warn(self, msg, *args, **kwargs): |
|
281 |
""" Same as msg() but as warning. """ |
|
282 |
prefix = "% 3d: " % self.handler_id |
|
283 |
self.logger.log(logging.WARN, "%s%s" % (prefix, msg), *args, **kwargs) |
|
431 | 284 |
|
432 | 285 |
# |
433 |
# Main WebSocketServer methods
|
|
286 |
# Main WebSocketRequestHandler methods
|
|
434 | 287 |
# |
435 | 288 |
def send_frames(self, bufs=None): |
436 | 289 |
""" Encode and send WebSocket frames. Any frames already |
... | ... | |
444 | 297 |
|
445 | 298 |
if bufs: |
446 | 299 |
for buf in bufs: |
447 |
if self.version.startswith("hybi"): |
|
448 |
if self.base64: |
|
449 |
encbuf, lenhead, lentail = self.encode_hybi( |
|
450 |
buf, opcode=1, base64=True) |
|
451 |
else: |
|
452 |
encbuf, lenhead, lentail = self.encode_hybi( |
|
453 |
buf, opcode=2, base64=False) |
|
454 |
|
|
300 |
if self.base64: |
|
301 |
encbuf, lenhead, lentail = self.encode_hybi(buf, opcode=1, base64=True) |
|
455 | 302 |
else: |
456 |
encbuf, lenhead, lentail = self.encode_hixie(buf)
|
|
303 |
encbuf, lenhead, lentail = self.encode_hybi(buf, opcode=2, base64=False)
|
|
457 | 304 |
|
458 | 305 |
if self.rec: |
459 | 306 |
self.rec.write("%s,\n" % |
... | ... | |
465 | 312 |
while self.send_parts: |
466 | 313 |
# Send pending frames |
467 | 314 |
buf = self.send_parts.pop(0) |
468 |
sent = self.client.send(buf)
|
|
315 |
sent = self.request.send(buf)
|
|
469 | 316 |
|
470 | 317 |
if sent == len(buf): |
471 |
self.traffic("<") |
|
318 |
self.print_traffic("<")
|
|
472 | 319 |
else: |
473 |
self.traffic("<.") |
|
320 |
self.print_traffic("<.")
|
|
474 | 321 |
self.send_parts.insert(0, buf[sent:]) |
475 | 322 |
break |
476 | 323 |
|
... | ... | |
487 | 334 |
bufs = [] |
488 | 335 |
tdelta = int(time.time()*1000) - self.start_time |
489 | 336 |
|
490 |
buf = self.client.recv(self.buffer_size)
|
|
337 |
buf = self.request.recv(self.buffer_size)
|
|
491 | 338 |
if len(buf) == 0: |
492 | 339 |
closed = {'code': 1000, 'reason': "Client closed abruptly"} |
493 | 340 |
return bufs, closed |
... | ... | |
498 | 345 |
self.recv_part = None |
499 | 346 |
|
500 | 347 |
while buf: |
501 |
if self.version.startswith("hybi"): |
|
502 |
|
|
503 |
frame = self.decode_hybi(buf, base64=self.base64) |
|
504 |
#print("Received buf: %s, frame: %s" % (repr(buf), frame)) |
|
505 |
|
|
506 |
if frame['payload'] == None: |
|
507 |
# Incomplete/partial frame |
|
508 |
self.traffic("}.") |
|
509 |
if frame['left'] > 0: |
|
510 |
self.recv_part = buf[-frame['left']:] |
|
511 |
break |
|
512 |
else: |
|
513 |
if frame['opcode'] == 0x8: # connection close |
|
514 |
closed = {'code': frame['close_code'], |
|
515 |
'reason': frame['close_reason']} |
|
516 |
break |
|
517 |
|
|
348 |
frame = self.decode_hybi(buf, base64=self.base64, |
|
349 |
logger=self.logger) |
|
350 |
#self.msg("Received buf: %s, frame: %s", repr(buf), frame) |
|
351 |
|
|
352 |
if frame['payload'] == None: |
|
353 |
# Incomplete/partial frame |
|
354 |
self.print_traffic("}.") |
|
355 |
if frame['left'] > 0: |
|
356 |
self.recv_part = buf[-frame['left']:] |
|
357 |
break |
|
518 | 358 |
else: |
519 |
if buf[0:2] == s2b('\xff\x00'):
|
|
520 |
closed = {'code': 1000,
|
|
521 |
'reason': "Client sent orderly close frame"}
|
|
359 |
if frame['opcode'] == 0x8: # connection close
|
|
360 |
closed = {'code': frame['close_code'],
|
|
361 |
'reason': frame['close_reason']}
|
|
522 | 362 |
break |
523 | 363 |
|
524 |
elif buf[0:2] == s2b('\x00\xff'): |
|
525 |
buf = buf[2:] |
|
526 |
continue # No-op |
|
527 |
|
|
528 |
elif buf.count(s2b('\xff')) == 0: |
|
529 |
# Partial frame |
|
530 |
self.traffic("}.") |
|
531 |
self.recv_part = buf |
|
532 |
break |
|
533 |
|
|
534 |
frame = self.decode_hixie(buf) |
|
535 |
|
|
536 |
self.traffic("}") |
|
364 |
self.print_traffic("}") |
|
537 | 365 |
|
538 | 366 |
if self.rec: |
539 | 367 |
start = frame['hlen'] |
540 | 368 |
end = frame['hlen'] + frame['length'] |
541 | 369 |
if frame['masked']: |
542 |
recbuf = WebSocketServer.unmask(buf, frame['hlen'],
|
|
370 |
recbuf = WebSocketRequestHandler.unmask(buf, frame['hlen'],
|
|
543 | 371 |
frame['length']) |
544 | 372 |
else: |
545 | 373 |
recbuf = buf[frame['hlen']:frame['hlen'] + |
... | ... | |
560 | 388 |
def send_close(self, code=1000, reason=''): |
561 | 389 |
""" Send a WebSocket orderly close frame. """ |
562 | 390 |
|
563 |
if self.version.startswith("hybi"): |
|
564 |
msg = pack(">H%ds" % len(reason), code, reason) |
|
565 |
|
|
566 |
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False) |
|
567 |
self.client.send(buf) |
|
568 |
|
|
569 |
elif self.version == "hixie-76": |
|
570 |
buf = s2b('\xff\x00') |
|
571 |
self.client.send(buf) |
|
572 |
|
|
573 |
# No orderly close for 75 |
|
391 |
msg = pack(">H%ds" % len(reason), code, reason) |
|
392 |
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False) |
|
393 |
self.request.send(buf) |
|
574 | 394 |
|
575 |
def do_websocket_handshake(self, headers, path): |
|
576 |
h = self.headers = headers |
|
577 |
self.path = path |
|
395 |
def do_websocket_handshake(self): |
|
396 |
h = self.headers |
|
578 | 397 |
|
579 | 398 |
prot = 'WebSocket-Protocol' |
580 | 399 |
protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',') |
... | ... | |
589 | 408 |
if ver in ['7', '8', '13']: |
590 | 409 |
self.version = "hybi-%02d" % int(ver) |
591 | 410 |
else: |
592 |
raise self.EClose('Unsupported protocol version %s' % ver) |
|
411 |
self.send_error(400, "Unsupported protocol version %s" % ver) |
|
412 |
return False |
|
593 | 413 |
|
594 | 414 |
key = h['Sec-WebSocket-Key'] |
595 | 415 |
|
... | ... | |
599 | 419 |
elif 'base64' in protocols: |
600 | 420 |
self.base64 = True |
601 | 421 |
else: |
602 |
raise self.EClose("Client must support 'binary' or 'base64' protocol") |
|
422 |
self.send_error(400, "Client must support 'binary' or 'base64' protocol") |
|
423 |
return False |
|
603 | 424 |
|
604 | 425 |
# Generate the hash value for the accept header |
605 | 426 |
accept = b64encode(sha1(s2b(key + self.GUID)).digest()) |
606 | 427 |
|
607 |
response = self.server_handshake_hybi % b2s(accept) |
|
428 |
self.send_response(101, "Switching Protocols") |
|
429 |
self.send_header("Upgrade", "websocket") |
|
430 |
self.send_header("Connection", "Upgrade") |
|
431 |
self.send_header("Sec-WebSocket-Accept", b2s(accept)) |
|
608 | 432 |
if self.base64: |
609 |
response += "Sec-WebSocket-Protocol: base64\r\n" |
|
433 |
self.send_header("Sec-WebSocket-Protocol", "base64") |
|
434 |
else: |
|
435 |
self.send_header("Sec-WebSocket-Protocol", "binary") |
|
436 |
self.end_headers() |
|
437 |
return True |
|
438 |
else: |
|
439 |
self.send_error(400, "Missing Sec-WebSocket-Version header. Hixie protocols not supported.") |
|
440 |
|
|
441 |
return False |
|
442 |
|
|
443 |
def handle_websocket(self): |
|
444 |
"""Upgrade a connection to Websocket, if requested. If this succeeds, |
|
445 |
new_websocket_client() will be called. Otherwise, False is returned. |
|
446 |
""" |
|
447 |
if (self.headers.get('upgrade') and |
|
448 |
self.headers.get('upgrade').lower() == 'websocket'): |
|
449 |
|
|
450 |
if not self.do_websocket_handshake(): |
|
451 |
return False |
|
452 |
|
|
453 |
# Indicate to server that a Websocket upgrade was done |
|
454 |
self.server.ws_connection = True |
|
455 |
# Initialize per client settings |
|
456 |
self.send_parts = [] |
|
457 |
self.recv_part = None |
|
458 |
self.start_time = int(time.time()*1000) |
|
459 |
|
|
460 |
# client_address is empty with, say, UNIX domain sockets |
|
461 |
client_addr = "" |
|
462 |
is_ssl = False |
|
463 |
try: |
|
464 |
client_addr = self.client_address[0] |
|
465 |
is_ssl = self.client_address[2] |
|
466 |
except IndexError: |
|
467 |
pass |
|
468 |
|
|
469 |
if is_ssl: |
|
470 |
self.stype = "SSL/TLS (wss://)" |
|
610 | 471 |
else: |
611 |
response += "Sec-WebSocket-Protocol: binary\r\n" |
|
612 |
response += "\r\n" |
|
472 |
self.stype = "Plain non-SSL (ws://)" |
|
473 |
|
|
474 |
self.log_message("%s: %s WebSocket connection", client_addr, |
|
475 |
self.stype) |
|
476 |
self.log_message("%s: Version %s, base64: '%s'", client_addr, |
|
477 |
self.version, self.base64) |
|
478 |
if self.path != '/': |
|
479 |
self.log_message("%s: Path: '%s'", client_addr, self.path) |
|
480 |
|
|
481 |
if self.record: |
|
482 |
# Record raw frame data as JavaScript array |
|
483 |
fname = "%s.%s" % (self.record, |
|
484 |
self.handler_id) |
|
485 |
self.log_message("opening record file: %s", fname) |
|
486 |
self.rec = open(fname, 'w+') |
|
487 |
encoding = "binary" |
|
488 |
if self.base64: encoding = "base64" |
|
489 |
self.rec.write("var VNC_frame_encoding = '%s';\n" |
|
490 |
% encoding) |
|
491 |
self.rec.write("var VNC_frame_data = [\n") |
|
613 | 492 |
|
493 |
try: |
|
494 |
self.new_websocket_client() |
|
495 |
except self.CClose: |
|
496 |
# Close the client |
|
497 |
_, exc, _ = sys.exc_info() |
|
498 |
self.send_close(exc.args[0], exc.args[1]) |
|
499 |
return True |
|
614 | 500 |
else: |
615 |
# Hixie version of the protocol (75 or 76) |
|
501 |
return False |
|
502 |
|
|
503 |
def do_GET(self): |
|
504 |
"""Handle GET request. Calls handle_websocket(). If unsuccessful, |
|
505 |
and web server is enabled, SimpleHTTPRequestHandler.do_GET will be called.""" |
|
506 |
if not self.handle_websocket(): |
|
507 |
if self.only_upgrade: |
|
508 |
self.send_error(405, "Method Not Allowed") |
|
509 |
else: |
|
510 |
SimpleHTTPRequestHandler.do_GET(self) |
|
511 |
|
|
512 |
def list_directory(self, path): |
|
513 |
if self.file_only: |
|
514 |
self.send_error(404, "No such file") |
|
515 |
else: |
|
516 |
return SimpleHTTPRequestHandler.list_directory(self, path) |
|
517 |
|
|
518 |
def new_websocket_client(self): |
|
519 |
""" Do something with a WebSockets client connection. """ |
|
520 |
raise Exception("WebSocketRequestHandler.new_websocket_client() must be overloaded") |
|
521 |
|
|
522 |
def do_HEAD(self): |
|
523 |
if self.only_upgrade: |
|
524 |
self.send_error(405, "Method Not Allowed") |
|
525 |
else: |
|
526 |
SimpleHTTPRequestHandler.do_HEAD(self) |
|
527 |
|
|
528 |
def finish(self): |
|
529 |
if self.rec: |
|
530 |
self.rec.write("'EOF'];\n") |
|
531 |
self.rec.close() |
|
532 |
|
|
533 |
def handle(self): |
|
534 |
# When using run_once, we have a single process, so |
|
535 |
# we cannot loop in BaseHTTPRequestHandler.handle; we |
|
536 |
# must return and handle new connections |
|
537 |
if self.run_once: |
|
538 |
self.handle_one_request() |
|
539 |
else: |
|
540 |
SimpleHTTPRequestHandler.handle(self) |
|
541 |
|
|
542 |
def log_request(self, code='-', size='-'): |
|
543 |
if self.verbose: |
|
544 |
SimpleHTTPRequestHandler.log_request(self, code, size) |
|
545 |
|
|
616 | 546 |
|
617 |
if h.get('key3'): |
|
618 |
trailer = self.gen_md5(h) |
|
619 |
pre = "Sec-" |
|
620 |
self.version = "hixie-76" |
|
547 |
class WebSocketServer(object): |
|
548 |
""" |
|
549 |
WebSockets server class. |
|
550 |
As an alternative, the standard library SocketServer can be used |
|
551 |
""" |
|
552 |
|
|
553 |
policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n""" |
|
554 |
log_prefix = "websocket" |
|
555 |
|
|
556 |
# An exception before the WebSocket connection was established |
|
557 |
class EClose(Exception): |
|
558 |
pass |
|
559 |
|
|
560 |
class Terminate(Exception): |
|
561 |
pass |
|
562 |
|
|
563 |
def __init__(self, RequestHandlerClass, listen_host='', |
|
564 |
listen_port=None, source_is_ipv6=False, |
|
565 |
verbose=False, cert='', key='', ssl_only=None, |
|
566 |
daemon=False, record='', web='', |
|
567 |
file_only=False, |
|
568 |
run_once=False, timeout=0, idle_timeout=0, traffic=False, |
|
569 |
tcp_keepalive=True, tcp_keepcnt=None, tcp_keepidle=None, |
|
570 |
tcp_keepintvl=None): |
|
571 |
|
|
572 |
# settings |
|
573 |
self.RequestHandlerClass = RequestHandlerClass |
|
574 |
self.verbose = verbose |
|
575 |
self.listen_host = listen_host |
|
576 |
self.listen_port = listen_port |
|
577 |
self.prefer_ipv6 = source_is_ipv6 |
|
578 |
self.ssl_only = ssl_only |
|
579 |
self.daemon = daemon |
|
580 |
self.run_once = run_once |
|
581 |
self.timeout = timeout |
|
582 |
self.idle_timeout = idle_timeout |
|
583 |
self.traffic = traffic |
|
584 |
|
|
585 |
self.launch_time = time.time() |
|
586 |
self.ws_connection = False |
|
587 |
self.handler_id = 1 |
|
588 |
|
|
589 |
self.logger = self.get_logger() |
|
590 |
self.tcp_keepalive = tcp_keepalive |
|
591 |
self.tcp_keepcnt = tcp_keepcnt |
|
592 |
self.tcp_keepidle = tcp_keepidle |
|
593 |
self.tcp_keepintvl = tcp_keepintvl |
|
594 |
|
|
595 |
# Make paths settings absolute |
|
596 |
self.cert = os.path.abspath(cert) |
|
597 |
self.key = self.web = self.record = '' |
|
598 |
if key: |
|
599 |
self.key = os.path.abspath(key) |
|
600 |
if web: |
|
601 |
self.web = os.path.abspath(web) |
|
602 |
if record: |
|
603 |
self.record = os.path.abspath(record) |
|
604 |
|
|
605 |
if self.web: |
|
606 |
os.chdir(self.web) |
|
607 |
self.only_upgrade = not self.web |
|
608 |
|
|
609 |
# Sanity checks |
|
610 |
if not ssl and self.ssl_only: |
|
611 |
raise Exception("No 'ssl' module and SSL-only specified") |
|
612 |
if self.daemon and not resource: |
|
613 |
raise Exception("Module 'resource' required to daemonize") |
|
614 |
|
|
615 |
# Show configuration |
|
616 |
self.msg("WebSocket server settings:") |
|
617 |
self.msg(" - Listen on %s:%s", |
|
618 |
self.listen_host, self.listen_port) |
|
619 |
self.msg(" - Flash security policy server") |
|
620 |
if self.web: |
|
621 |
self.msg(" - Web server. Web root: %s", self.web) |
|
622 |
if ssl: |
|
623 |
if os.path.exists(self.cert): |
|
624 |
self.msg(" - SSL/TLS support") |
|
625 |
if self.ssl_only: |
|
626 |
self.msg(" - Deny non-SSL/TLS connections") |
|
621 | 627 |
else: |
622 |
trailer = "" |
|
623 |
pre = "" |
|
624 |
self.version = "hixie-75" |
|
628 |
self.msg(" - No SSL/TLS support (no cert file)") |
|
629 |
else: |
|
630 |
self.msg(" - No SSL/TLS support (no 'ssl' module)") |
|
631 |
if self.daemon: |
|
632 |
self.msg(" - Backgrounding (daemon)") |
|
633 |
if self.record: |
|
634 |
self.msg(" - Recording to '%s.*'", self.record) |
|
625 | 635 |
|
626 |
# We only support base64 in Hixie era |
|
627 |
self.base64 = True |
|
636 |
# |
|
637 |
# WebSocketServer static methods |
|
638 |
# |
|
628 | 639 |
|
629 |
response = self.server_handshake_hixie % (pre, |
|
630 |
h['Origin'], pre, self.scheme, h['Host'], path) |
|
640 |
@staticmethod |
|
641 |
def get_logger(): |
|
642 |
return logging.getLogger("%s.%s" % ( |
|
643 |
WebSocketServer.log_prefix, |
|
644 |
WebSocketServer.__class__.__name__)) |
|
631 | 645 |
|
632 |
if 'base64' in protocols: |
|
633 |
response += "%sWebSocket-Protocol: base64\r\n" % pre |
|
646 |
@staticmethod |
|
647 |
def socket(host, port=None, connect=False, prefer_ipv6=False, |
|
648 |
unix_socket=None, use_ssl=False, tcp_keepalive=True, |
|
649 |
tcp_keepcnt=None, tcp_keepidle=None, tcp_keepintvl=None): |
|
650 |
""" Resolve a host (and optional port) to an IPv4 or IPv6 |
|
651 |
address. Create a socket. Bind to it if listen is set, |
|
652 |
otherwise connect to it. Return the socket. |
|
653 |
""" |
|
654 |
flags = 0 |
|
655 |
if host == '': |
|
656 |
host = None |
|
657 |
if connect and not (port or unix_socket): |
|
658 |
raise Exception("Connect mode requires a port") |
|
659 |
if use_ssl and not ssl: |
|
660 |
raise Exception("SSL socket requested but Python SSL module not loaded."); |
|
661 |
if not connect and use_ssl: |
|
662 |
raise Exception("SSL only supported in connect mode (for now)") |
|
663 |
if not connect: |
|
664 |
flags = flags | socket.AI_PASSIVE |
|
665 |
|
|
666 |
if not unix_socket: |
|
667 |
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, |
|
668 |
socket.IPPROTO_TCP, flags) |
|
669 |
if not addrs: |
|
670 |
raise Exception("Could not resolve host '%s'" % host) |
|
671 |
addrs.sort(key=lambda x: x[0]) |
|
672 |
if prefer_ipv6: |
|
673 |
addrs.reverse() |
|
674 |
sock = socket.socket(addrs[0][0], addrs[0][1]) |
|
675 |
|
|
676 |
if tcp_keepalive: |
|
677 |
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) |
|
678 |
if tcp_keepcnt: |
|
679 |
sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT, |
|
680 |
tcp_keepcnt) |
|
681 |
if tcp_keepidle: |
|
682 |
sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, |
|
683 |
tcp_keepidle) |
|
684 |
if tcp_keepintvl: |
|
685 |
sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, |
|
686 |
tcp_keepintvl) |
|
687 |
|
|
688 |
if connect: |
|
689 |
sock.connect(addrs[0][4]) |
|
690 |
if use_ssl: |
|
691 |
sock = ssl.wrap_socket(sock) |
|
634 | 692 |
else: |
635 |
self.msg("Warning: client does not report 'base64' protocol support") |
|
636 |
response += "\r\n" + trailer |
|
693 |
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
|
694 |
sock.bind(addrs[0][4]) |
|
695 |
sock.listen(100) |
|
696 |
else: |
|
697 |
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) |
|
698 |
sock.connect(unix_socket) |
|
699 |
|
|
700 |
return sock |
|
637 | 701 |
|
638 |
return response |
|
702 |
@staticmethod |
|
703 |
def daemonize(keepfd=None, chdir='/'): |
|
704 |
os.umask(0) |
|
705 |
if chdir: |
|
706 |
os.chdir(chdir) |
|
707 |
else: |
|
708 |
os.chdir('/') |
|
709 |
os.setgid(os.getgid()) # relinquish elevations |
|
710 |
os.setuid(os.getuid()) # relinquish elevations |
|
639 | 711 |
|
712 |
# Double fork to daemonize |
|
713 |
if os.fork() > 0: os._exit(0) # Parent exits |
|
714 |
os.setsid() # Obtain new process group |
|
715 |
if os.fork() > 0: os._exit(0) # Parent exits |
|
716 |
|
|
717 |
# Signal handling |
|
718 |
signal.signal(signal.SIGTERM, signal.SIG_IGN) |
|
719 |
signal.signal(signal.SIGINT, signal.SIG_IGN) |
|
720 |
|
|
721 |
# Close open files |
|
722 |
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] |
|
723 |
if maxfd == resource.RLIM_INFINITY: maxfd = 256 |
|
724 |
for fd in reversed(range(maxfd)): |
|
725 |
try: |
|
726 |
if fd != keepfd: |
|
727 |
os.close(fd) |
|
728 |
except OSError: |
|
729 |
_, exc, _ = sys.exc_info() |
|
730 |
if exc.errno != errno.EBADF: raise |
|
731 |
|
|
732 |
# Redirect I/O to /dev/null |
|
733 |
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno()) |
|
734 |
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno()) |
|
735 |
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno()) |
|
640 | 736 |
|
641 | 737 |
def do_handshake(self, sock, address): |
642 | 738 |
""" |
... | ... | |
655 | 751 |
- Send a WebSockets handshake server response. |
656 | 752 |
- Return the socket for this WebSocket client. |
657 | 753 |
""" |
658 |
stype = "" |
|
659 | 754 |
ready = select.select([sock], [], [], 3)[0] |
660 | 755 |
|
661 | 756 |
|
... | ... | |
699 | 794 |
else: |
700 | 795 |
raise |
701 | 796 |
|
702 |
self.scheme = "wss" |
|
703 |
stype = "SSL/TLS (wss://)" |
|
704 |
|
|
705 | 797 |
elif self.ssl_only: |
706 | 798 |
raise self.EClose("non-SSL connection received but disallowed") |
707 | 799 |
|
708 | 800 |
else: |
709 | 801 |
retsock = sock |
710 |
self.scheme = "ws" |
|
711 |
stype = "Plain non-SSL (ws://)" |
|
712 | 802 |
|
713 |
wsh = WSRequestHandler(retsock, address, not self.web) |
|
714 |
if wsh.last_code == 101: |
|
715 |
# Continue on to handle WebSocket upgrade |
|
716 |
pass |
|
717 |
elif wsh.last_code == 405: |
|
718 |
raise self.EClose("Normal web request received but disallowed") |
|
719 |
elif wsh.last_code < 200 or wsh.last_code >= 300: |
|
720 |
raise self.EClose(wsh.last_message) |
|
721 |
elif self.verbose: |
|
722 |
raise self.EClose(wsh.last_message) |
|
723 |
else: |
|
724 |
raise self.EClose("") |
|
803 |
# If the address is like (host, port), we are extending it |
|
804 |
# with a flag indicating SSL. Not many other options |
|
805 |
# available... |
|
806 |
if len(address) == 2: |
|
807 |
address = (address[0], address[1], (retsock != sock)) |
|
725 | 808 |
|
726 |
response = self.do_websocket_handshake(wsh.headers, wsh.path)
|
|
809 |
self.RequestHandlerClass(retsock, address, self)
|
|
727 | 810 |
|
728 |
self.msg("%s: %s WebSocket connection" % (address[0], stype)) |
|
729 |
self.msg("%s: Version %s, base64: '%s'" % (address[0], |
|
730 |
self.version, self.base64)) |
|
731 |
if self.path != '/': |
|
732 |
self.msg("%s: Path: '%s'" % (address[0], self.path)) |
|
811 |
# Return the WebSockets socket which may be SSL wrapped |
|
812 |
return retsock |
|
733 | 813 |
|
814 |
# |
|
815 |
# WebSocketServer logging/output functions |
|
816 |
# |
|
734 | 817 |
|
735 |
# Send server WebSockets handshake response
|
|
736 |
#self.msg("sending response [%s]" % response)
|
|
737 |
retsock.send(s2b(response))
|
|
818 |
def msg(self, *args, **kwargs):
|
|
819 |
""" Output message as info """
|
|
820 |
self.logger.log(logging.INFO, *args, **kwargs)
|
|
738 | 821 |
|
739 |
# Return the WebSockets socket which may be SSL wrapped |
|
740 |
return retsock |
|
822 |
def vmsg(self, *args, **kwargs): |
|
823 |
""" Same as msg() but as debug. """ |
|
824 |
self.logger.log(logging.DEBUG, *args, **kwargs) |
|
825 |
|
|
826 |
def warn(self, *args, **kwargs): |
|
827 |
""" Same as msg() but as warning. """ |
|
828 |
self.logger.log(logging.WARN, *args, **kwargs) |
|
741 | 829 |
|
742 | 830 |
|
743 | 831 |
# |
... | ... | |
752 | 840 |
#self.vmsg("Running poll()") |
753 | 841 |
pass |
754 | 842 |
|
843 |
def terminate(self): |
|
844 |
raise self.Terminate() |
|
845 |
|
|
846 |
def multiprocessing_SIGCHLD(self, sig, stack): |
|
847 |
self.vmsg('Reaping zombies, active child count is %s', len(multiprocessing.active_children())) |
|
848 |
|
|
755 | 849 |
def fallback_SIGCHLD(self, sig, stack): |
756 | 850 |
# Reap zombies when using os.fork() (python 2.4) |
757 | 851 |
self.vmsg("Got SIGCHLD, reaping zombies") |
... | ... | |
765 | 859 |
|
766 | 860 |
def do_SIGINT(self, sig, stack): |
767 | 861 |
self.msg("Got SIGINT, exiting") |
768 |
sys.exit(0) |
|
862 |
self.terminate() |
|
863 |
|
|
864 |
def do_SIGTERM(self, sig, stack): |
|
865 |
self.msg("Got SIGTERM, exiting") |
|
866 |
self.terminate() |
|
769 | 867 |
|
770 | 868 |
def top_new_client(self, startsock, address): |
771 | 869 |
""" Do something with a WebSockets client connection. """ |
772 |
# Initialize per client settings |
|
773 |
self.send_parts = [] |
|
774 |
self.recv_part = None |
|
775 |
self.base64 = False |
|
776 |
self.rec = None |
|
777 |
self.start_time = int(time.time()*1000) |
|
778 |
|
|
779 | 870 |
# handler process |
871 |
client = None |
|
780 | 872 |
try: |
781 | 873 |
try: |
782 |
self.client = self.do_handshake(startsock, address) |
|
783 |
|
|
784 |
if self.record: |
|
785 |
# Record raw frame data as JavaScript array |
|
786 |
fname = "%s.%s" % (self.record, |
|
787 |
self.handler_id) |
|
788 |
self.msg("opening record file: %s" % fname) |
|
789 |
self.rec = open(fname, 'w+') |
|
790 |
encoding = "binary" |
|
791 |
if self.base64: encoding = "base64" |
|
792 |
self.rec.write("var VNC_frame_encoding = '%s';\n" |
|
793 |
% encoding) |
|
794 |
self.rec.write("var VNC_frame_data = [\n") |
|
795 |
|
|
796 |
self.ws_connection = True |
|
797 |
self.new_client() |
|
798 |
except self.CClose: |
|
799 |
# Close the client |
|
800 |
_, exc, _ = sys.exc_info() |
|
801 |
if self.client: |
|
802 |
self.send_close(exc.args[0], exc.args[1]) |
|
874 |
client = self.do_handshake(startsock, address) |
|
803 | 875 |
except self.EClose: |
804 | 876 |
_, exc, _ = sys.exc_info() |
805 | 877 |
# Connection was not a WebSockets connection |
806 | 878 |
if exc.args[0]: |
807 | 879 |
self.msg("%s: %s" % (address[0], exc.args[0])) |
880 |
except WebSocketServer.Terminate: |
|
881 |
raise |
|
808 | 882 |
except Exception: |
809 | 883 |
_, exc, _ = sys.exc_info() |
810 | 884 |
self.msg("handler exception: %s" % str(exc)) |
811 |
if self.verbose: |
|
812 |
self.msg(traceback.format_exc()) |
|
885 |
self.vmsg("exception", exc_info=True) |
|
813 | 886 |
finally: |
814 |
if self.rec: |
|
815 |
self.rec.write("'EOF'];\n") |
|
816 |
self.rec.close() |
|
817 | 887 |
|
818 |
if self.client and self.client != startsock:
|
|
888 |
if client and client != startsock:
|
|
819 | 889 |
# Close the SSL wrapped socket |
820 | 890 |
# Original socket closed by caller |
821 |
self.client.close() |
|
822 |
|
|
823 |
def new_client(self): |
|
824 |
""" Do something with a WebSockets client connection. """ |
|
825 |
raise("WebSocketServer.new_client() must be overloaded") |
|
891 |
client.close() |
|
826 | 892 |
|
827 | 893 |
def start_server(self): |
828 | 894 |
""" |
829 | 895 |
Daemonize if requested. Listen for for connections. Run |
830 | 896 |
do_handshake() method for each connection. If the connection |
831 |
is a WebSockets client then call new_client() method (which must |
|
897 |
is a WebSockets client then call new_websocket_client() method (which must
|
|
832 | 898 |
be overridden) for each new client connection. |
833 | 899 |
""" |
834 |
lsock = self.socket(self.listen_host, self.listen_port, False, self.prefer_ipv6) |
|
900 |
lsock = self.socket(self.listen_host, self.listen_port, False, |
|
901 |
self.prefer_ipv6, |
|
902 |
tcp_keepalive=self.tcp_keepalive, |
|
903 |
tcp_keepcnt=self.tcp_keepcnt, |
|
904 |
tcp_keepidle=self.tcp_keepidle, |
|
905 |
tcp_keepintvl=self.tcp_keepintvl) |
|
835 | 906 |
|
836 | 907 |
if self.daemon: |
837 | 908 |
self.daemonize(keepfd=lsock.fileno(), chdir=self.web) |
838 | 909 |
|
839 | 910 |
self.started() # Some things need to happen after daemonizing |
840 | 911 |
|
841 |
# Allow override of SIGINT |
|
912 |
# Allow override of signals |
|
913 |
original_signals = { |
|
914 |
signal.SIGINT: signal.getsignal(signal.SIGINT), |
|
915 |
signal.SIGTERM: signal.getsignal(signal.SIGTERM), |
|
916 |
signal.SIGCHLD: signal.getsignal(signal.SIGCHLD), |
|
917 |
} |
|
842 | 918 |
signal.signal(signal.SIGINT, self.do_SIGINT) |
919 |
signal.signal(signal.SIGTERM, self.do_SIGTERM) |
|
843 | 920 |
if not multiprocessing: |
844 | 921 |
# os.fork() (python 2.4) child reaper |
845 | 922 |
signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD) |
923 |
else: |
|
924 |
# make sure that _cleanup is called when children die |
|
925 |
# by calling active_children on SIGCHLD |
|
926 |
signal.signal(signal.SIGCHLD, self.multiprocessing_SIGCHLD) |
|
846 | 927 |
|
847 | 928 |
last_active_time = self.launch_time |
848 |
while True:
|
|
849 |
try:
|
|
929 |
try:
|
|
930 |
while True:
|
|
850 | 931 |
try: |
851 |
self.client = None |
|
852 |
startsock = None |
|
853 |
pid = err = 0 |
|
854 |
child_count = 0 |
|
855 |
|
|
856 |
if multiprocessing and self.idle_timeout: |
|
857 |
child_count = len(multiprocessing.active_children()) |
|
858 |
|
|
859 |
time_elapsed = time.time() - self.launch_time |
|
860 |
if self.timeout and time_elapsed > self.timeout: |
|
861 |
self.msg('listener exit due to --timeout %s' |
|
862 |
% self.timeout) |
|
863 |
break |
|
864 |
|
|
865 |
if self.idle_timeout: |
|
866 |
idle_time = 0 |
|
867 |
if child_count == 0: |
|
868 |
idle_time = time.time() - last_active_time |
|
869 |
else: |
|
870 |
idle_time = 0 |
|
871 |
last_active_time = time.time() |
|
872 |
|
|
873 |
if idle_time > self.idle_timeout and child_count == 0: |
|
874 |
self.msg('listener exit due to --idle-timeout %s' |
|
875 |
% self.idle_timeout) |
|
876 |
break |
|
877 |
|
|
878 | 932 |
try: |
879 |
self.poll() |
|
933 |
startsock = None |
|
934 |
pid = err = 0 |
|
935 |
child_count = 0 |
|
936 |
|
|
937 |
if multiprocessing: |
|
938 |
# Collect zombie child processes |
|
939 |
child_count = len(multiprocessing.active_children()) |
|
940 |
|
|
941 |
time_elapsed = time.time() - self.launch_time |
|
942 |
if self.timeout and time_elapsed > self.timeout: |
|
943 |
self.msg('listener exit due to --timeout %s' |
|
944 |
% self.timeout) |
|
945 |
break |
|
880 | 946 |
|
881 |
ready = select.select([lsock], [], [], 1)[0] |
|
882 |
if lsock in ready: |
|
883 |
startsock, address = lsock.accept() |
|
884 |
else: |
|
885 |
continue |
|
886 |
except Exception: |
|
887 |
_, exc, _ = sys.exc_info() |
|
888 |
if hasattr(exc, 'errno'): |
|
889 |
err = exc.errno |
|
890 |
elif hasattr(exc, 'args'): |
|
891 |
err = exc.args[0] |
|
892 |
else: |
|
893 |
err = exc[0] |
|
894 |
if err == errno.EINTR: |
|
895 |
self.vmsg("Ignoring interrupted syscall") |
|
896 |
continue |
|
897 |
else: |
|
947 |
if self.idle_timeout: |
|
948 |
idle_time = 0 |
|
949 |
if child_count == 0: |
|
950 |
idle_time = time.time() - last_active_time |
|
951 |
else: |
|
952 |
idle_time = 0 |
|
953 |
last_active_time = time.time() |
|
954 |
|
|
955 |
if idle_time > self.idle_timeout and child_count == 0: |
|
956 |
self.msg('listener exit due to --idle-timeout %s' |
|
957 |
% self.idle_timeout) |
|
958 |
break |
|
959 |
|
|
960 |
try: |
|
961 |
self.poll() |
|
962 |
|
|
963 |
ready = select.select([lsock], [], [], 1)[0] |
|
964 |
if lsock in ready: |
|
965 |
startsock, address = lsock.accept() |
|
966 |
else: |
|
967 |
continue |
|
968 |
except self.Terminate: |
|
898 | 969 |
raise |
899 |
|
|
900 |
if self.run_once: |
|
901 |
# Run in same process if run_once |
|
902 |
self.top_new_client(startsock, address) |
|
903 |
if self.ws_connection : |
|
904 |
self.msg('%s: exiting due to --run-once' |
|
905 |
% address[0]) |
|
906 |
break |
|
907 |
elif multiprocessing: |
|
908 |
self.vmsg('%s: new handler Process' % address[0]) |
|
909 |
p = multiprocessing.Process( |
|
910 |
target=self.top_new_client, |
|
911 |
args=(startsock, address)) |
|
912 |
p.start() |
|
913 |
# child will not return |
|
914 |
else: |
|
915 |
# python 2.4 |
|
916 |
self.vmsg('%s: forking handler' % address[0]) |
|
917 |
pid = os.fork() |
|
918 |
if pid == 0: |
|
919 |
# child handler process |
|
970 |
except Exception: |
|
971 |
_, exc, _ = sys.exc_info() |
|
972 |
if hasattr(exc, 'errno'): |
|
973 |
err = exc.errno |
|
974 |
elif hasattr(exc, 'args'): |
|
975 |
err = exc.args[0] |
|
976 |
else: |
|
977 |
err = exc[0] |
|
978 |
if err == errno.EINTR: |
|
979 |
self.vmsg("Ignoring interrupted syscall") |
|
980 |
continue |
|
981 |
else: |
|
982 |
raise |
|
983 |
|
|
984 |
if self.run_once: |
|
985 |
# Run in same process if run_once |
|
920 | 986 |
self.top_new_client(startsock, address) |
921 |
break # child process exits |
|
922 |
|
|
923 |
# parent process |
|
924 |
self.handler_id += 1 |
|
925 |
|
|
926 |
except KeyboardInterrupt: |
|
927 |
_, exc, _ = sys.exc_info() |
|
928 |
print("In KeyboardInterrupt") |
|
929 |
pass |
|
930 |
except SystemExit: |
|
931 |
_, exc, _ = sys.exc_info() |
|
932 |
print("In SystemExit") |
|
933 |
break |
|
934 |
except Exception: |
|
935 |
_, exc, _ = sys.exc_info() |
|
936 |
self.msg("handler exception: %s" % str(exc)) |
|
937 |
if self.verbose: |
|
938 |
self.msg(traceback.format_exc()) |
|
939 |
|
|
940 |
finally: |
|
941 |
if startsock: |
|
942 |
startsock.close() |
|
943 |
|
|
987 |
if self.ws_connection : |
|
988 |
self.msg('%s: exiting due to --run-once' |
|
989 |
% address[0]) |
|
990 |
break |
|
991 |
elif multiprocessing: |
|
992 |
self.vmsg('%s: new handler Process' % address[0]) |
|
993 |
p = multiprocessing.Process( |
|
994 |
target=self.top_new_client, |
|
995 |
args=(startsock, address)) |
|
996 |
p.start() |
|
997 |
# child will not return |
|
998 |
else: |
|
999 |
# python 2.4 |
|
1000 |
self.vmsg('%s: forking handler' % address[0]) |
|
1001 |
pid = os.fork() |
|
1002 |
if pid == 0: |
|
1003 |
# child handler process |
|
1004 |
self.top_new_client(startsock, address) |
|
1005 |
break # child process exits |
|
1006 |
|
|
1007 |
# parent process |
|
1008 |
self.handler_id += 1 |
|
1009 |
|
|
1010 |
except (self.Terminate, SystemExit, KeyboardInterrupt): |
|
1011 |
self.msg("In exit") |
|
1012 |
break |
|
1013 |
except Exception: |
|
1014 |
self.msg("handler exception: %s", str(exc)) |
|
1015 |
self.vmsg("exception", exc_info=True) |
|
944 | 1016 |
|
945 |
# HTTP handler with WebSocket upgrade support |
|
946 |
class WSRequestHandler(SimpleHTTPRequestHandler): |
|
947 |
def __init__(self, req, addr, only_upgrade=False): |
|
948 |
self.only_upgrade = only_upgrade # only allow upgrades |
|
949 |
SimpleHTTPRequestHandler.__init__(self, req, addr, object()) |
|
1017 |
finally: |
|
1018 |
if startsock: |
|
1019 |
startsock.close() |
|
1020 |
finally: |
|
1021 |
# Close listen port |
|
1022 |
self.vmsg("Closing socket listening at %s:%s", |
|
1023 |
self.listen_host, self.listen_port) |
|
1024 |
lsock.close() |
|
950 | 1025 |
|
951 |
def do_GET(self): |
|
952 |
if (self.headers.get('upgrade') and |
|
953 |
self.headers.get('upgrade').lower() == 'websocket'): |
|
954 |
|
|
955 |
if (self.headers.get('sec-websocket-key1') or |
|
956 |
self.headers.get('websocket-key1')): |
|
957 |
# For Hixie-76 read out the key hash |
|
958 |
self.headers.__setitem__('key3', self.rfile.read(8)) |
|
959 |
|
|
960 |
# Just indicate that an WebSocket upgrade is needed |
|
961 |
self.last_code = 101 |
|
962 |
self.last_message = "101 Switching Protocols" |
|
963 |
elif self.only_upgrade: |
|
964 |
# Normal web request responses are disabled |
|
965 |
self.last_code = 405 |
|
966 |
self.last_message = "405 Method Not Allowed" |
|
967 |
else: |
|
968 |
SimpleHTTPRequestHandler.do_GET(self) |
|
1026 |
# Restore signals |
|
1027 |
for sig, func in original_signals.items(): |
|
1028 |
signal.signal(sig, func) |
|
969 | 1029 |
|
970 |
def send_response(self, code, message=None): |
|
971 |
# Save the status code |
|
972 |
self.last_code = code |
|
973 |
SimpleHTTPRequestHandler.send_response(self, code, message) |
|
974 | 1030 |
|
975 |
def log_message(self, f, *args): |
|
976 |
# Save instead of printing |
|
977 |
self.last_message = f % args |
Also available in: Unified diff