]>
git.proxmox.com Git - mirror_novnc.git/blob - utils/websocket.py
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)
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
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
19 import os
, sys
, time
, errno
, signal
, socket
, traceback
, select
21 from cgi
import parse_qsl
22 from base64
import b64encode
, b64decode
24 # Imports that vary by python version
26 # python 3.0 differences
27 if sys
.hexversion
> 0x3000000:
28 b2s
= lambda buf
: buf
.decode('latin_1')
29 s2b
= lambda s
: s
.encode('latin_1')
32 b2s
= lambda buf
: buf
# No-op
33 s2b
= lambda s
: s
# No-op
34 s2a
= lambda s
: [ord(c
) for c
in s
]
35 try: from io
import StringIO
36 except: from cStringIO
import StringIO
37 try: from http
.server
import SimpleHTTPRequestHandler
38 except: from SimpleHTTPServer
import SimpleHTTPRequestHandler
39 try: from urllib
.parse
import urlsplit
40 except: from urlparse
import urlsplit
42 # python 2.6 differences
43 try: from hashlib
import md5
, sha1
44 except: from md5
import md5
; from sha
import sha
as sha1
46 # python 2.5 differences
48 from struct
import pack
, unpack_from
50 from struct
import pack
51 def unpack_from(fmt
, buf
, offset
=0):
52 slice = buffer(buf
, offset
, struct
.calcsize(fmt
))
53 return struct
.unpack(fmt
, slice)
55 # Degraded functionality if these imports are missing
56 for mod
, sup
in [('numpy', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'),
57 ('multiprocessing', 'Multi-Processing'),
58 ('resource', 'daemonizing')]:
60 globals()[mod
] = __import__(mod
)
63 print("WARNING: no '%s' module, %s is slower or disabled" % (
65 if multiprocessing
and sys
.platform
== 'win32':
66 # make sockets pickle-able/inheritable
67 import multiprocessing
.reduction
70 class WebSocketServer(object):
72 WebSockets server class.
73 Must be sub-classed with new_client method definition.
78 server_handshake_hixie
= """HTTP/1.1 101 Web Socket Protocol Handshake\r
81 %sWebSocket-Origin: %s\r
82 %sWebSocket-Location: %s://%s%s\r
85 server_handshake_hybi
= """HTTP/1.1 101 Switching Protocols\r
88 Sec-WebSocket-Accept: %s\r
91 GUID
= "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
93 policy_response
= """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
95 class EClose(Exception):
98 def __init__(self
, listen_host
='', listen_port
=None, source_is_ipv6
=False,
99 verbose
=False, cert
='', key
='', ssl_only
=None,
100 daemon
=False, record
='', web
='',
101 run_once
=False, timeout
=0):
104 self
.verbose
= verbose
105 self
.listen_host
= listen_host
106 self
.listen_port
= listen_port
107 self
.ssl_only
= ssl_only
109 self
.run_once
= run_once
110 self
.timeout
= timeout
112 self
.launch_time
= time
.time()
113 self
.ws_connection
= False
116 # Make paths settings absolute
117 self
.cert
= os
.path
.abspath(cert
)
118 self
.key
= self
.web
= self
.record
= ''
120 self
.key
= os
.path
.abspath(key
)
122 self
.web
= os
.path
.abspath(web
)
124 self
.record
= os
.path
.abspath(record
)
130 if not ssl
and self
.ssl_only
:
131 raise Exception("No 'ssl' module and SSL-only specified")
132 if self
.daemon
and not resource
:
133 raise Exception("Module 'resource' required to daemonize")
136 print("WebSocket server settings:")
137 print(" - Listen on %s:%s" % (
138 self
.listen_host
, self
.listen_port
))
139 print(" - Flash security policy server")
141 print(" - Web server. Web root: %s" % self
.web
)
143 if os
.path
.exists(self
.cert
):
144 print(" - SSL/TLS support")
146 print(" - Deny non-SSL/TLS connections")
148 print(" - No SSL/TLS support (no cert file)")
150 print(" - No SSL/TLS support (no 'ssl' module)")
152 print(" - Backgrounding (daemon)")
154 print(" - Recording to '%s.*'" % self
.record
)
157 # WebSocketServer static methods
161 def socket(host
, port
=None, connect
=False, prefer_ipv6
=False):
162 """ Resolve a host (and optional port) to an IPv4 or IPv6
163 address. Create a socket. Bind to it if listen is set,
164 otherwise connect to it. Return the socket.
169 if connect
and not port
:
170 raise Exception("Connect mode requires a port")
172 flags
= flags | socket
.AI_PASSIVE
173 addrs
= socket
.getaddrinfo(host
, port
, 0, socket
.SOCK_STREAM
,
174 socket
.IPPROTO_TCP
, flags
)
176 raise Exception("Could resolve host '%s'" % host
)
177 addrs
.sort(key
=lambda x
: x
[0])
180 sock
= socket
.socket(addrs
[0][0], addrs
[0][1])
182 sock
.connect(addrs
[0][4])
184 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEADDR
, 1)
185 sock
.bind(addrs
[0][4])
190 def daemonize(keepfd
=None, chdir
='/'):
196 os
.setgid(os
.getgid()) # relinquish elevations
197 os
.setuid(os
.getuid()) # relinquish elevations
199 # Double fork to daemonize
200 if os
.fork() > 0: os
._exit
(0) # Parent exits
201 os
.setsid() # Obtain new process group
202 if os
.fork() > 0: os
._exit
(0) # Parent exits
205 def terminate(a
,b
): os
._exit
(0)
206 signal
.signal(signal
.SIGTERM
, terminate
)
207 signal
.signal(signal
.SIGINT
, signal
.SIG_IGN
)
210 maxfd
= resource
.getrlimit(resource
.RLIMIT_NOFILE
)[1]
211 if maxfd
== resource
.RLIM_INFINITY
: maxfd
= 256
212 for fd
in reversed(range(maxfd
)):
217 _
, exc
, _
= sys
.exc_info()
218 if exc
.errno
!= errno
.EBADF
: raise
220 # Redirect I/O to /dev/null
221 os
.dup2(os
.open(os
.devnull
, os
.O_RDWR
), sys
.stdin
.fileno())
222 os
.dup2(os
.open(os
.devnull
, os
.O_RDWR
), sys
.stdout
.fileno())
223 os
.dup2(os
.open(os
.devnull
, os
.O_RDWR
), sys
.stderr
.fileno())
227 pstart
= f
['hlen'] + 4
228 pend
= pstart
+ f
['length']
232 mask
= numpy
.frombuffer(buf
, dtype
=numpy
.dtype('<u4'),
233 offset
=f
['hlen'], count
=1)
234 data
= numpy
.frombuffer(buf
, dtype
=numpy
.dtype('<u4'),
235 offset
=pstart
, count
=int(f
['length'] / 4))
236 #b = numpy.bitwise_xor(data, mask).data
237 b
= numpy
.bitwise_xor(data
, mask
).tostring()
240 #print("Partial unmask")
241 mask
= numpy
.frombuffer(buf
, dtype
=numpy
.dtype('B'),
242 offset
=f
['hlen'], count
=(f
['length'] % 4))
243 data
= numpy
.frombuffer(buf
, dtype
=numpy
.dtype('B'),
244 offset
=pend
- (f
['length'] % 4),
245 count
=(f
['length'] % 4))
246 c
= numpy
.bitwise_xor(data
, mask
).tostring()
250 data
= array
.array('B')
251 mask
= s2a(f
['mask'])
252 data
.fromstring(buf
[pstart
:pend
])
253 for i
in range(len(data
)):
254 data
[i
] ^
= mask
[i
% 4]
255 return data
.tostring()
258 def encode_hybi(buf
, opcode
, base64
=False):
259 """ Encode a HyBi style WebSocket frame.
262 0x1 - text frame (base64 encode buf)
263 0x2 - binary frame (use raw buf)
264 0x8 - connection close
271 b1
= 0x80 |
(opcode
& 0x0f) # FIN + opcode
272 payload_len
= len(buf
)
273 if payload_len
<= 125:
274 header
= pack('>BB', b1
, payload_len
)
275 elif payload_len
> 125 and payload_len
< 65536:
276 header
= pack('>BBH', b1
, 126, payload_len
)
277 elif payload_len
>= 65536:
278 header
= pack('>BBQ', b1
, 127, payload_len
)
280 #print("Encoded: %s" % repr(header + buf))
282 return header
+ buf
, len(header
), 0
285 def decode_hybi(buf
, base64
=False):
286 """ Decode HyBi style WebSocket packets.
290 'mask' : 32_bit_number,
291 'hlen' : header_bytes_number,
292 'length' : payload_bytes_number,
293 'payload' : decoded_buffer,
294 'left' : bytes_left_number,
295 'close_code' : number,
296 'close_reason' : string}
307 'close_reason' : None}
313 return f
# Incomplete frame header
315 b1
, b2
= unpack_from(">BB", buf
)
316 f
['opcode'] = b1
& 0x0f
317 f
['fin'] = (b1
& 0x80) >> 7
318 has_mask
= (b2
& 0x80) >> 7
320 f
['length'] = b2
& 0x7f
322 if f
['length'] == 126:
325 return f
# Incomplete frame header
326 (f
['length'],) = unpack_from('>xxH', buf
)
327 elif f
['length'] == 127:
330 return f
# Incomplete frame header
331 (f
['length'],) = unpack_from('>xxQ', buf
)
333 full_len
= f
['hlen'] + has_mask
* 4 + f
['length']
335 if blen
< full_len
: # Incomplete frame
336 return f
# Incomplete frame header
338 # Number of bytes that are part of the next frame(s)
339 f
['left'] = blen
- full_len
344 f
['mask'] = buf
[f
['hlen']:f
['hlen']+4]
345 f
['payload'] = WebSocketServer
.unmask(buf
, f
)
347 print("Unmasked frame: %s" % repr(buf
))
348 f
['payload'] = buf
[(f
['hlen'] + has_mask
* 4):full_len
]
350 if base64
and f
['opcode'] in [1, 2]:
352 f
['payload'] = b64decode(f
['payload'])
354 print("Exception while b64decoding buffer: %s" %
358 if f
['opcode'] == 0x08:
360 f
['close_code'] = unpack_from(">H", f
['payload'])
362 f
['close_reason'] = f
['payload'][2:]
367 def encode_hixie(buf
):
368 return s2b("\x00" + b2s(b64encode(buf
)) + "\xff"), 1, 1
371 def decode_hixie(buf
):
372 end
= buf
.find(s2b('\xff'))
373 return {'payload': b64decode(buf
[1:end
]),
376 'left': len(buf
) - (end
+ 1)}
381 """ Generate hash value for WebSockets hixie-76. """
382 key1
= keys
['Sec-WebSocket-Key1']
383 key2
= keys
['Sec-WebSocket-Key2']
385 spaces1
= key1
.count(" ")
386 spaces2
= key2
.count(" ")
387 num1
= int("".join([c
for c
in key1
if c
.isdigit()])) / spaces1
388 num2
= int("".join([c
for c
in key2
if c
.isdigit()])) / spaces2
390 return b2s(md5(pack('>II8s',
391 int(num1
), int(num2
), key3
)).digest())
394 # WebSocketServer logging/output functions
397 def traffic(self
, token
="."):
398 """ Show traffic flow in verbose mode. """
399 if self
.verbose
and not self
.daemon
:
400 sys
.stdout
.write(token
)
404 """ Output message with handler_id prefix. """
406 print("% 3d: %s" % (self
.handler_id
, msg
))
409 """ Same as msg() but only if verbose. """
414 # Main WebSocketServer methods
416 def send_frames(self
, bufs
=None):
417 """ Encode and send WebSocket frames. Any frames already
418 queued will be sent first. If buf is not set then only queued
419 frames will be sent. Returns the number of pending frames that
420 could not be fully sent. If returned pending frames is greater
421 than 0, then the caller should call again when the socket is
424 tdelta
= int(time
.time()*1000) - self
.start_time
428 if self
.version
.startswith("hybi"):
430 encbuf
, lenhead
, lentail
= self
.encode_hybi(
431 buf
, opcode
=1, base64
=True)
433 encbuf
, lenhead
, lentail
= self
.encode_hybi(
434 buf
, opcode
=2, base64
=False)
437 encbuf
, lenhead
, lentail
= self
.encode_hixie(buf
)
440 self
.rec
.write("%s,\n" %
442 + encbuf
[lenhead
:-lentail
]))
444 self
.send_parts
.append(encbuf
)
446 while self
.send_parts
:
447 # Send pending frames
448 buf
= self
.send_parts
.pop(0)
449 sent
= self
.client
.send(buf
)
455 self
.send_parts
.insert(0, buf
[sent
:])
458 return len(self
.send_parts
)
460 def recv_frames(self
):
461 """ Receive and decode WebSocket frames.
464 (bufs_list, closed_string)
469 tdelta
= int(time
.time()*1000) - self
.start_time
471 buf
= self
.client
.recv(self
.buffer_size
)
473 closed
= "Client closed abruptly"
477 # Add partially received frames to current read buffer
478 buf
= self
.recv_part
+ buf
479 self
.recv_part
= None
482 if self
.version
.startswith("hybi"):
484 frame
= self
.decode_hybi(buf
, base64
=self
.base64
)
485 #print("Received buf: %s, frame: %s" % (repr(buf), frame))
487 if frame
['payload'] == None:
488 # Incomplete/partial frame
490 if frame
['left'] > 0:
491 self
.recv_part
= buf
[-frame
['left']:]
494 if frame
['opcode'] == 0x8: # connection close
495 closed
= "Client closed, reason: %s - %s" % (
497 frame
['close_reason'])
501 if buf
[0:2] == s2b('\xff\x00'):
502 closed
= "Client sent orderly close frame"
505 elif buf
[0:2] == s2b('\x00\xff'):
509 elif buf
.count(s2b('\xff')) == 0:
515 frame
= self
.decode_hixie(buf
)
520 start
= frame
['hlen']
521 end
= frame
['hlen'] + frame
['length']
522 self
.rec
.write("%s,\n" %
523 repr("}%s}" % tdelta
+ buf
[start
:end
]))
526 bufs
.append(frame
['payload'])
529 buf
= buf
[-frame
['left']:]
535 def send_close(self
, code
=None, reason
=''):
536 """ Send a WebSocket orderly close frame. """
538 if self
.version
.startswith("hybi"):
541 msg
= pack(">H%ds" % (len(reason
)), code
)
543 buf
, h
, t
= self
.encode_hybi(msg
, opcode
=0x08, base64
=False)
544 self
.client
.send(buf
)
546 elif self
.version
== "hixie-76":
547 buf
= s2b('\xff\x00')
548 self
.client
.send(buf
)
550 # No orderly close for 75
552 def do_handshake(self
, sock
, address
):
554 do_handshake does the following:
555 - Peek at the first few bytes from the socket.
556 - If the connection is Flash policy request then answer it,
557 close the socket and return.
558 - If the connection is an HTTPS/SSL/TLS connection then SSL
560 - Read from the (possibly wrapped) socket.
561 - If we have received a HTTP GET request and the webserver
562 functionality is enabled, answer it, close the socket and
564 - Assume we have a WebSockets connection, parse the client
566 - Send a WebSockets handshake server response.
567 - Return the socket for this WebSocket client.
572 ready
= select
.select([sock
], [], [], 3)[0]
574 raise self
.EClose("ignoring socket not ready")
575 # Peek, but do not read the data so that we have a opportunity
576 # to SSL wrap the socket first
577 handshake
= sock
.recv(1024, socket
.MSG_PEEK
)
578 #self.msg("Handshake [%s]" % handshake)
581 raise self
.EClose("ignoring empty handshake")
583 elif handshake
.startswith(s2b("<policy-file-request/>")):
584 # Answer Flash policy request
585 handshake
= sock
.recv(1024)
586 sock
.send(s2b(self
.policy_response
))
587 raise self
.EClose("Sending flash policy response")
589 elif handshake
[0] in ("\x16", "\x80", 22, 128):
590 # SSL wrap the connection
592 raise self
.EClose("SSL connection but no 'ssl' module")
593 if not os
.path
.exists(self
.cert
):
594 raise self
.EClose("SSL connection but '%s' not found"
598 retsock
= ssl
.wrap_socket(
604 _
, x
, _
= sys
.exc_info()
605 if x
.args
[0] == ssl
.SSL_ERROR_EOF
:
607 raise self
.EClose(x
.args
[1])
609 raise self
.EClose("Got SSL_ERROR_EOF")
614 stype
= "SSL/TLS (wss://)"
617 raise self
.EClose("non-SSL connection received but disallowed")
622 stype
= "Plain non-SSL (ws://)"
624 wsh
= WSRequestHandler(retsock
, address
, not self
.web
)
625 if wsh
.last_code
== 101:
626 # Continue on to handle WebSocket upgrade
628 elif wsh
.last_code
== 405:
629 raise self
.EClose("Normal web request received but disallowed")
630 elif wsh
.last_code
< 200 or wsh
.last_code
>= 300:
631 raise self
.EClose(wsh
.last_message
)
633 raise self
.EClose(wsh
.last_message
)
635 raise self
.EClose("")
637 h
= self
.headers
= wsh
.headers
638 path
= self
.path
= wsh
.path
640 prot
= 'WebSocket-Protocol'
641 protocols
= h
.get('Sec-'+prot
, h
.get(prot
, '')).split(',')
643 ver
= h
.get('Sec-WebSocket-Version')
645 # HyBi/IETF version of the protocol
647 # HyBi-07 report version 7
648 # HyBi-08 - HyBi-12 report version 8
649 # HyBi-13 reports version 13
650 if ver
in ['7', '8', '13']:
651 self
.version
= "hybi-%02d" % int(ver
)
653 raise self
.EClose('Unsupported protocol version %s' % ver
)
655 key
= h
['Sec-WebSocket-Key']
657 # Choose binary if client supports it
658 if 'binary' in protocols
:
660 elif 'base64' in protocols
:
663 raise self
.EClose("Client must support 'binary' or 'base64' protocol")
665 # Generate the hash value for the accept header
666 accept
= b64encode(sha1(s2b(key
+ self
.GUID
)).digest())
668 response
= self
.server_handshake_hybi
% b2s(accept
)
670 response
+= "Sec-WebSocket-Protocol: base64\r\n"
672 response
+= "Sec-WebSocket-Protocol: binary\r\n"
676 # Hixie version of the protocol (75 or 76)
679 trailer
= self
.gen_md5(h
)
681 self
.version
= "hixie-76"
685 self
.version
= "hixie-75"
687 # We only support base64 in Hixie era
690 response
= self
.server_handshake_hixie
% (pre
,
691 h
['Origin'], pre
, scheme
, h
['Host'], path
)
693 if 'base64' in protocols
:
694 response
+= "%sWebSocket-Protocol: base64\r\n" % pre
696 self
.msg("Warning: client does not report 'base64' protocol support")
697 response
+= "\r\n" + trailer
699 self
.msg("%s: %s WebSocket connection" % (address
[0], stype
))
700 self
.msg("%s: Version %s, base64: '%s'" % (address
[0],
701 self
.version
, self
.base64
))
703 self
.msg("%s: Path: '%s'" % (address
[0], self
.path
))
706 # Send server WebSockets handshake response
707 #self.msg("sending response [%s]" % response)
708 retsock
.send(s2b(response
))
710 # Return the WebSockets socket which may be SSL wrapped
715 # Events that can/should be overridden in sub-classes
718 """ Called after WebSockets startup """
719 self
.vmsg("WebSockets server started")
722 """ Run periodically while waiting for connections. """
723 #self.vmsg("Running poll()")
726 def fallback_SIGCHLD(self
, sig
, stack
):
727 # Reap zombies when using os.fork() (python 2.4)
728 self
.vmsg("Got SIGCHLD, reaping zombies")
730 result
= os
.waitpid(-1, os
.WNOHANG
)
732 self
.vmsg("Reaped child process %s" % result
[0])
733 result
= os
.waitpid(-1, os
.WNOHANG
)
737 def do_SIGINT(self
, sig
, stack
):
738 self
.msg("Got SIGINT, exiting")
741 def top_new_client(self
, startsock
, address
):
742 """ Do something with a WebSockets client connection. """
743 # Initialize per client settings
745 self
.recv_part
= None
748 self
.start_time
= int(time
.time()*1000)
753 self
.client
= self
.do_handshake(startsock
, address
)
756 # Record raw frame data as JavaScript array
757 fname
= "%s.%s" % (self
.record
,
759 self
.msg("opening record file: %s" % fname
)
760 self
.rec
= open(fname
, 'w+')
761 self
.rec
.write("var VNC_frame_data = [\n")
763 self
.ws_connection
= True
766 _
, exc
, _
= sys
.exc_info()
767 # Connection was not a WebSockets connection
769 self
.msg("%s: %s" % (address
[0], exc
.args
[0]))
771 _
, exc
, _
= sys
.exc_info()
772 self
.msg("handler exception: %s" % str(exc
))
774 self
.msg(traceback
.format_exc())
777 self
.rec
.write("'EOF']\n")
780 if self
.client
and self
.client
!= startsock
:
783 def new_client(self
):
784 """ Do something with a WebSockets client connection. """
785 raise("WebSocketServer.new_client() must be overloaded")
787 def start_server(self
):
789 Daemonize if requested. Listen for for connections. Run
790 do_handshake() method for each connection. If the connection
791 is a WebSockets client then call new_client() method (which must
792 be overridden) for each new client connection.
794 lsock
= self
.socket(self
.listen_host
, self
.listen_port
)
797 self
.daemonize(keepfd
=lsock
.fileno(), chdir
=self
.web
)
799 self
.started() # Some things need to happen after daemonizing
801 # Allow override of SIGINT
802 signal
.signal(signal
.SIGINT
, self
.do_SIGINT
)
803 if not multiprocessing
:
804 # os.fork() (python 2.4) child reaper
805 signal
.signal(signal
.SIGCHLD
, self
.fallback_SIGCHLD
)
814 time_elapsed
= time
.time() - self
.launch_time
815 if self
.timeout
and time_elapsed
> self
.timeout
:
816 self
.msg('listener exit due to --timeout %s'
823 ready
= select
.select([lsock
], [], [], 1)[0]
825 startsock
, address
= lsock
.accept()
829 _
, exc
, _
= sys
.exc_info()
830 if hasattr(exc
, 'errno'):
832 elif hasattr(exc
, 'args'):
836 if err
== errno
.EINTR
:
837 self
.vmsg("Ignoring interrupted syscall")
843 # Run in same process if run_once
844 self
.top_new_client(startsock
, address
)
845 if self
.ws_connection
:
846 self
.msg('%s: exiting due to --run-once'
849 elif multiprocessing
:
850 self
.vmsg('%s: new handler Process' % address
[0])
851 p
= multiprocessing
.Process(
852 target
=self
.top_new_client
,
853 args
=(startsock
, address
))
855 # child will not return
858 self
.vmsg('%s: forking handler' % address
[0])
861 # child handler process
862 self
.top_new_client(startsock
, address
)
863 break # child process exits
868 except KeyboardInterrupt:
869 _
, exc
, _
= sys
.exc_info()
870 print("In KeyboardInterrupt")
873 _
, exc
, _
= sys
.exc_info()
874 print("In SystemExit")
877 _
, exc
, _
= sys
.exc_info()
878 self
.msg("handler exception: %s" % str(exc
))
880 self
.msg(traceback
.format_exc())
887 # HTTP handler with WebSocket upgrade support
888 class WSRequestHandler(SimpleHTTPRequestHandler
):
889 def __init__(self
, req
, addr
, only_upgrade
=False):
890 self
.only_upgrade
= only_upgrade
# only allow upgrades
891 SimpleHTTPRequestHandler
.__init
__(self
, req
, addr
, object())
894 if (self
.headers
.get('upgrade') and
895 self
.headers
.get('upgrade').lower() == 'websocket'):
897 if (self
.headers
.get('sec-websocket-key1') or
898 self
.headers
.get('websocket-key1')):
899 # For Hixie-76 read out the key hash
900 self
.headers
.__setitem
__('key3', self
.rfile
.read(8))
902 # Just indicate that an WebSocket upgrade is needed
904 self
.last_message
= "101 Switching Protocols"
905 elif self
.only_upgrade
:
906 # Normal web request responses are disabled
908 self
.last_message
= "405 Method Not Allowed"
910 SimpleHTTPRequestHandler
.do_GET(self
)
912 def send_response(self
, code
, message
=None):
913 # Save the status code
914 self
.last_code
= code
915 SimpleHTTPRequestHandler
.send_response(self
, code
, message
)
917 def log_message(self
, f
, *args
):
918 # Save instead of printing
919 self
.last_message
= f
% args