]> git.proxmox.com Git - mirror_novnc.git/blob - utils/websocket.py
sync with websockify
[mirror_novnc.git] / utils / websocket.py
1 #!/usr/bin/env python
2
3 '''
4 Python WebSocket library with support for "wss://" encryption.
5 Copyright 2011 Joel Martin
6 Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
7
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
11 - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
12
13 You can make a cert/key with openssl using:
14 openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
15 as taken from http://docs.python.org/dev/library/ssl.html#certificates
16
17 '''
18
19 import os, sys, time, errno, signal, socket, traceback, select
20 import array, struct
21 from base64 import b64encode, b64decode
22
23 # Imports that vary by python version
24
25 # python 3.0 differences
26 if sys.hexversion > 0x3000000:
27 b2s = lambda buf: buf.decode('latin_1')
28 s2b = lambda s: s.encode('latin_1')
29 s2a = lambda s: s
30 else:
31 b2s = lambda buf: buf # No-op
32 s2b = lambda s: s # No-op
33 s2a = lambda s: [ord(c) for c in s]
34 try: from io import StringIO
35 except: from cStringIO import StringIO
36 try: from http.server import SimpleHTTPRequestHandler
37 except: from SimpleHTTPServer import SimpleHTTPRequestHandler
38
39 # python 2.6 differences
40 try: from hashlib import md5, sha1
41 except: from md5 import md5; from sha import sha as sha1
42
43 # python 2.5 differences
44 try:
45 from struct import pack, unpack_from
46 except:
47 from struct import pack
48 def unpack_from(fmt, buf, offset=0):
49 slice = buffer(buf, offset, struct.calcsize(fmt))
50 return struct.unpack(fmt, slice)
51
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')]:
56 try:
57 globals()[mod] = __import__(mod)
58 except ImportError:
59 globals()[mod] = None
60 print("WARNING: no '%s' module, %s is slower or disabled" % (
61 mod, sup))
62 if multiprocessing and sys.platform == 'win32':
63 # make sockets pickle-able/inheritable
64 import multiprocessing.reduction
65
66
67 class WebSocketServer(object):
68 """
69 WebSockets server class.
70 Must be sub-classed with new_client method definition.
71 """
72
73 buffer_size = 65536
74
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 GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
90
91 policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
92
93 # An exception before the WebSocket connection was established
94 class EClose(Exception):
95 pass
96
97 # An exception while the WebSocket client was connected
98 class CClose(Exception):
99 pass
100
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)
225
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
236
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())
241
242 @staticmethod
243 def unmask(buf, hlen, plen):
244 pstart = hlen + 4
245 pend = pstart + plen
246 if numpy:
247 b = c = s2b('')
248 if plen >= 4:
249 mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
250 offset=hlen, count=1)
251 data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
252 offset=pstart, count=int(plen / 4))
253 #b = numpy.bitwise_xor(data, mask).data
254 b = numpy.bitwise_xor(data, mask).tostring()
255
256 if plen % 4:
257 #print("Partial unmask")
258 mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
259 offset=hlen, count=(plen % 4))
260 data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
261 offset=pend - (plen % 4),
262 count=(plen % 4))
263 c = numpy.bitwise_xor(data, mask).tostring()
264 return b + c
265 else:
266 # Slower fallback
267 mask = buf[hlen:hlen+4]
268 data = array.array('B')
269 mask = s2a(mask)
270 data.fromstring(buf[pstart:pend])
271 for i in range(len(data)):
272 data[i] ^= mask[i % 4]
273 return data.tostring()
274
275 @staticmethod
276 def encode_hybi(buf, opcode, base64=False):
277 """ Encode a HyBi style WebSocket frame.
278 Optional opcode:
279 0x0 - continuation
280 0x1 - text frame (base64 encode buf)
281 0x2 - binary frame (use raw buf)
282 0x8 - connection close
283 0x9 - ping
284 0xA - pong
285 """
286 if base64:
287 buf = b64encode(buf)
288
289 b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
290 payload_len = len(buf)
291 if payload_len <= 125:
292 header = pack('>BB', b1, payload_len)
293 elif payload_len > 125 and payload_len < 65536:
294 header = pack('>BBH', b1, 126, payload_len)
295 elif payload_len >= 65536:
296 header = pack('>BBQ', b1, 127, payload_len)
297
298 #print("Encoded: %s" % repr(header + buf))
299
300 return header + buf, len(header), 0
301
302 @staticmethod
303 def decode_hybi(buf, base64=False):
304 """ Decode HyBi style WebSocket packets.
305 Returns:
306 {'fin' : 0_or_1,
307 'opcode' : number,
308 'masked' : boolean,
309 'hlen' : header_bytes_number,
310 'length' : payload_bytes_number,
311 'payload' : decoded_buffer,
312 'left' : bytes_left_number,
313 'close_code' : number,
314 'close_reason' : string}
315 """
316
317 f = {'fin' : 0,
318 'opcode' : 0,
319 'masked' : False,
320 'hlen' : 2,
321 'length' : 0,
322 'payload' : None,
323 'left' : 0,
324 'close_code' : 1000,
325 'close_reason' : ''}
326
327 blen = len(buf)
328 f['left'] = blen
329
330 if blen < f['hlen']:
331 return f # Incomplete frame header
332
333 b1, b2 = unpack_from(">BB", buf)
334 f['opcode'] = b1 & 0x0f
335 f['fin'] = (b1 & 0x80) >> 7
336 f['masked'] = (b2 & 0x80) >> 7
337
338 f['length'] = b2 & 0x7f
339
340 if f['length'] == 126:
341 f['hlen'] = 4
342 if blen < f['hlen']:
343 return f # Incomplete frame header
344 (f['length'],) = unpack_from('>xxH', buf)
345 elif f['length'] == 127:
346 f['hlen'] = 10
347 if blen < f['hlen']:
348 return f # Incomplete frame header
349 (f['length'],) = unpack_from('>xxQ', buf)
350
351 full_len = f['hlen'] + f['masked'] * 4 + f['length']
352
353 if blen < full_len: # Incomplete frame
354 return f # Incomplete frame header
355
356 # Number of bytes that are part of the next frame(s)
357 f['left'] = blen - full_len
358
359 # Process 1 frame
360 if f['masked']:
361 # unmask payload
362 f['payload'] = WebSocketServer.unmask(buf, f['hlen'],
363 f['length'])
364 else:
365 print("Unmasked frame: %s" % repr(buf))
366 f['payload'] = buf[(f['hlen'] + f['masked'] * 4):full_len]
367
368 if base64 and f['opcode'] in [1, 2]:
369 try:
370 f['payload'] = b64decode(f['payload'])
371 except:
372 print("Exception while b64decoding buffer: %s" %
373 repr(buf))
374 raise
375
376 if f['opcode'] == 0x08:
377 if f['length'] >= 2:
378 f['close_code'] = unpack_from(">H", f['payload'])[0]
379 if f['length'] > 3:
380 f['close_reason'] = f['payload'][2:]
381
382 return f
383
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
412 #
413 # WebSocketServer logging/output functions
414 #
415
416 def traffic(self, token="."):
417 """ Show traffic flow in verbose mode. """
418 if self.verbose and not self.daemon:
419 sys.stdout.write(token)
420 sys.stdout.flush()
421
422 def msg(self, msg):
423 """ Output message with handler_id prefix. """
424 if not self.daemon:
425 print("% 3d: %s" % (self.handler_id, msg))
426
427 def vmsg(self, msg):
428 """ Same as msg() but only if verbose. """
429 if self.verbose:
430 self.msg(msg)
431
432 #
433 # Main WebSocketServer methods
434 #
435 def send_frames(self, bufs=None):
436 """ Encode and send WebSocket frames. Any frames already
437 queued will be sent first. If buf is not set then only queued
438 frames will be sent. Returns the number of pending frames that
439 could not be fully sent. If returned pending frames is greater
440 than 0, then the caller should call again when the socket is
441 ready. """
442
443 tdelta = int(time.time()*1000) - self.start_time
444
445 if bufs:
446 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
455 else:
456 encbuf, lenhead, lentail = self.encode_hixie(buf)
457
458 if self.rec:
459 self.rec.write("%s,\n" %
460 repr("{%s{" % tdelta
461 + encbuf[lenhead:len(encbuf)-lentail]))
462
463 self.send_parts.append(encbuf)
464
465 while self.send_parts:
466 # Send pending frames
467 buf = self.send_parts.pop(0)
468 sent = self.client.send(buf)
469
470 if sent == len(buf):
471 self.traffic("<")
472 else:
473 self.traffic("<.")
474 self.send_parts.insert(0, buf[sent:])
475 break
476
477 return len(self.send_parts)
478
479 def recv_frames(self):
480 """ Receive and decode WebSocket frames.
481
482 Returns:
483 (bufs_list, closed_string)
484 """
485
486 closed = False
487 bufs = []
488 tdelta = int(time.time()*1000) - self.start_time
489
490 buf = self.client.recv(self.buffer_size)
491 if len(buf) == 0:
492 closed = {'code': 1000, 'reason': "Client closed abruptly"}
493 return bufs, closed
494
495 if self.recv_part:
496 # Add partially received frames to current read buffer
497 buf = self.recv_part + buf
498 self.recv_part = None
499
500 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
518 else:
519 if buf[0:2] == s2b('\xff\x00'):
520 closed = {'code': 1000,
521 'reason': "Client sent orderly close frame"}
522 break
523
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("}")
537
538 if self.rec:
539 start = frame['hlen']
540 end = frame['hlen'] + frame['length']
541 if frame['masked']:
542 recbuf = WebSocketServer.unmask(buf, frame['hlen'],
543 frame['length'])
544 else:
545 recbuf = buf[frame['hlen']:frame['hlen'] +
546 frame['length']]
547 self.rec.write("%s,\n" %
548 repr("}%s}" % tdelta + recbuf))
549
550
551 bufs.append(frame['payload'])
552
553 if frame['left']:
554 buf = buf[-frame['left']:]
555 else:
556 buf = ''
557
558 return bufs, closed
559
560 def send_close(self, code=1000, reason=''):
561 """ Send a WebSocket orderly close frame. """
562
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
574
575 def do_websocket_handshake(self, headers, path):
576 h = self.headers = headers
577 self.path = path
578
579 prot = 'WebSocket-Protocol'
580 protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',')
581
582 ver = h.get('Sec-WebSocket-Version')
583 if ver:
584 # HyBi/IETF version of the protocol
585
586 # HyBi-07 report version 7
587 # HyBi-08 - HyBi-12 report version 8
588 # HyBi-13 reports version 13
589 if ver in ['7', '8', '13']:
590 self.version = "hybi-%02d" % int(ver)
591 else:
592 raise self.EClose('Unsupported protocol version %s' % ver)
593
594 key = h['Sec-WebSocket-Key']
595
596 # Choose binary if client supports it
597 if 'binary' in protocols:
598 self.base64 = False
599 elif 'base64' in protocols:
600 self.base64 = True
601 else:
602 raise self.EClose("Client must support 'binary' or 'base64' protocol")
603
604 # Generate the hash value for the accept header
605 accept = b64encode(sha1(s2b(key + self.GUID)).digest())
606
607 response = self.server_handshake_hybi % b2s(accept)
608 if self.base64:
609 response += "Sec-WebSocket-Protocol: base64\r\n"
610 else:
611 response += "Sec-WebSocket-Protocol: binary\r\n"
612 response += "\r\n"
613
614 else:
615 # Hixie version of the protocol (75 or 76)
616
617 if h.get('key3'):
618 trailer = self.gen_md5(h)
619 pre = "Sec-"
620 self.version = "hixie-76"
621 else:
622 trailer = ""
623 pre = ""
624 self.version = "hixie-75"
625
626 # We only support base64 in Hixie era
627 self.base64 = True
628
629 response = self.server_handshake_hixie % (pre,
630 h['Origin'], pre, self.scheme, h['Host'], path)
631
632 if 'base64' in protocols:
633 response += "%sWebSocket-Protocol: base64\r\n" % pre
634 else:
635 self.msg("Warning: client does not report 'base64' protocol support")
636 response += "\r\n" + trailer
637
638 return response
639
640
641 def do_handshake(self, sock, address):
642 """
643 do_handshake does the following:
644 - Peek at the first few bytes from the socket.
645 - If the connection is Flash policy request then answer it,
646 close the socket and return.
647 - If the connection is an HTTPS/SSL/TLS connection then SSL
648 wrap the socket.
649 - Read from the (possibly wrapped) socket.
650 - If we have received a HTTP GET request and the webserver
651 functionality is enabled, answer it, close the socket and
652 return.
653 - Assume we have a WebSockets connection, parse the client
654 handshake data.
655 - Send a WebSockets handshake server response.
656 - Return the socket for this WebSocket client.
657 """
658 stype = ""
659 ready = select.select([sock], [], [], 3)[0]
660
661
662 if not ready:
663 raise self.EClose("ignoring socket not ready")
664 # Peek, but do not read the data so that we have a opportunity
665 # to SSL wrap the socket first
666 handshake = sock.recv(1024, socket.MSG_PEEK)
667 #self.msg("Handshake [%s]" % handshake)
668
669 if handshake == "":
670 raise self.EClose("ignoring empty handshake")
671
672 elif handshake.startswith(s2b("<policy-file-request/>")):
673 # Answer Flash policy request
674 handshake = sock.recv(1024)
675 sock.send(s2b(self.policy_response))
676 raise self.EClose("Sending flash policy response")
677
678 elif handshake[0] in ("\x16", "\x80", 22, 128):
679 # SSL wrap the connection
680 if not ssl:
681 raise self.EClose("SSL connection but no 'ssl' module")
682 if not os.path.exists(self.cert):
683 raise self.EClose("SSL connection but '%s' not found"
684 % self.cert)
685 retsock = None
686 try:
687 retsock = ssl.wrap_socket(
688 sock,
689 server_side=True,
690 certfile=self.cert,
691 keyfile=self.key)
692 except ssl.SSLError:
693 _, x, _ = sys.exc_info()
694 if x.args[0] == ssl.SSL_ERROR_EOF:
695 if len(x.args) > 1:
696 raise self.EClose(x.args[1])
697 else:
698 raise self.EClose("Got SSL_ERROR_EOF")
699 else:
700 raise
701
702 self.scheme = "wss"
703 stype = "SSL/TLS (wss://)"
704
705 elif self.ssl_only:
706 raise self.EClose("non-SSL connection received but disallowed")
707
708 else:
709 retsock = sock
710 self.scheme = "ws"
711 stype = "Plain non-SSL (ws://)"
712
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("")
725
726 response = self.do_websocket_handshake(wsh.headers, wsh.path)
727
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))
733
734
735 # Send server WebSockets handshake response
736 #self.msg("sending response [%s]" % response)
737 retsock.send(s2b(response))
738
739 # Return the WebSockets socket which may be SSL wrapped
740 return retsock
741
742
743 #
744 # Events that can/should be overridden in sub-classes
745 #
746 def started(self):
747 """ Called after WebSockets startup """
748 self.vmsg("WebSockets server started")
749
750 def poll(self):
751 """ Run periodically while waiting for connections. """
752 #self.vmsg("Running poll()")
753 pass
754
755 def fallback_SIGCHLD(self, sig, stack):
756 # Reap zombies when using os.fork() (python 2.4)
757 self.vmsg("Got SIGCHLD, reaping zombies")
758 try:
759 result = os.waitpid(-1, os.WNOHANG)
760 while result[0]:
761 self.vmsg("Reaped child process %s" % result[0])
762 result = os.waitpid(-1, os.WNOHANG)
763 except (OSError):
764 pass
765
766 def do_SIGINT(self, sig, stack):
767 self.msg("Got SIGINT, exiting")
768 sys.exit(0)
769
770 def top_new_client(self, startsock, address):
771 """ 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 # handler process
780 try:
781 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])
803 except self.EClose:
804 _, exc, _ = sys.exc_info()
805 # Connection was not a WebSockets connection
806 if exc.args[0]:
807 self.msg("%s: %s" % (address[0], exc.args[0]))
808 except Exception:
809 _, exc, _ = sys.exc_info()
810 self.msg("handler exception: %s" % str(exc))
811 if self.verbose:
812 self.msg(traceback.format_exc())
813 finally:
814 if self.rec:
815 self.rec.write("'EOF'];\n")
816 self.rec.close()
817
818 if self.client and self.client != startsock:
819 # Close the SSL wrapped socket
820 # 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")
826
827 def start_server(self):
828 """
829 Daemonize if requested. Listen for for connections. Run
830 do_handshake() method for each connection. If the connection
831 is a WebSockets client then call new_client() method (which must
832 be overridden) for each new client connection.
833 """
834 lsock = self.socket(self.listen_host, self.listen_port, False, self.prefer_ipv6)
835
836 if self.daemon:
837 self.daemonize(keepfd=lsock.fileno(), chdir=self.web)
838
839 self.started() # Some things need to happen after daemonizing
840
841 # Allow override of SIGINT
842 signal.signal(signal.SIGINT, self.do_SIGINT)
843 if not multiprocessing:
844 # os.fork() (python 2.4) child reaper
845 signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
846
847 last_active_time = self.launch_time
848 while True:
849 try:
850 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 try:
879 self.poll()
880
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:
898 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
920 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
944
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())
950
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)
969
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
975 def log_message(self, f, *args):
976 # Save instead of printing
977 self.last_message = f % args