66from base64 import b64encode
77from hashlib import sha1
88
9-
109if sys .version_info [0 ] < 3 :
1110 from SocketServer import ThreadingMixIn , TCPServer , StreamRequestHandler
1211else :
1312 from socketserver import ThreadingMixIn , TCPServer , StreamRequestHandler
1413
1514
1615
16+ # ---------------------- Websocket bits in bytes -----------------------
17+ '''
18+ +-+-+-+-+-------+-+-------------+-------------------------------+
19+ 0 1 2 3
20+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
21+ +-+-+-+-+-------+-+-------------+-------------------------------+
22+ |F|R|R|R| opcode|M| Payload len | Extended payload length |
23+ |I|S|S|S| (4) |A| (7) | (16/64) |
24+ |N|V|V|V| |S| | (if payload len==126/127) |
25+ | |1|2|3| |K| | |
26+ +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
27+ | Extended payload length continued, if payload len == 127 |
28+ + - - - - - - - - - - - - - - - +-------------------------------+
29+ | Payload Data continued ... |
30+ +---------------------------------------------------------------+
31+ '''
32+
33+ FIN = 0x80
34+ OPCODE = 0x0f
35+ MASKED = 0x80
36+ PAYLOAD_LEN = 0x7f
37+ PAYLOAD_LEN_EXT16 = 0x7e
38+ PAYLOAD_LEN_EXT64 = 0x7f
39+
40+ OPCODE_TEXT = 0x01
41+ CLOSE_CONN = 0x8
42+
43+
44+
45+ # -------------------------------- API ---------------------------------
46+
1747class API ():
1848 def run_forever (self ):
1949 try :
@@ -44,12 +74,14 @@ def send_message_to_all(self, msg):
4474
4575
4676
77+ # ------------------------- Implementation -----------------------------
78+
4779class WebSocketsServer (ThreadingMixIn , TCPServer , API ):
4880
4981 allow_reuse_address = True
5082 daemon_threads = True # comment to keep threads alive until finished
5183
52- # clients is list of:
84+ # clients is list of dict :
5385 # {
5486 # 'id' : id,
5587 # 'handler' : handler,
@@ -116,32 +148,31 @@ def handle(self):
116148
117149
118150 def read_next_message (self ):
119-
120- b1 = self .rfile .read (1 )
121- b2 = self . rfile . read ( 1 )
122- FIN = ord (b1 ) & 0b10000000
123- OPCODE = ord (b1 ) & 0b00001111
124- MASKED = ord (b2 ) & 0b10000000
125- LENGTH = ord (b2 ) & 0b01111111
151+
152+ b1 , b2 = self .rfile .read (2 )
153+
154+ fin = ord (b1 ) & FIN
155+ opcode = ord (b1 ) & OPCODE
156+ masked = ord (b2 ) & MASKED
157+ payload_length = ord (b2 ) & PAYLOAD_LEN
126158
127159 if not b1 :
128160 print ("Client closed connection." )
129161 self .keep_alive = 0
130162 return
131- if OPCODE == 8 :
163+ if opcode == CLOSE_CONN :
132164 print ("Client asked to close connection." )
133165 self .keep_alive = 0
134166 return
135- if not MASKED :
167+ if not masked :
136168 print ("Client must always be masked." )
137169 self .keep_alive = 0
138170 return
139171
140- length = LENGTH
141- if LENGTH == 126 :
142- length = struct .unpack (">H" , self .rfile .read (2 ))[0 ]
143- elif LENGTH == 127 :
144- length = struct .unpack (">Q" , self .rfile .read (8 ))[0 ]
172+ if payload_length == 126 :
173+ payload_length = struct .unpack (">H" , self .rfile .read (2 ))[0 ]
174+ elif payload_length == 127 :
175+ payload_length = struct .unpack (">Q" , self .rfile .read (8 ))[0 ]
145176
146177 # python3 gives ordinal of byte directly
147178 if sys .version_info [0 ] < 3 :
@@ -150,7 +181,7 @@ def read_next_message(self):
150181 masks = [b for b in self .rfile .read (4 )]
151182
152183 decoded = ""
153- for char in self .rfile .read (length ):
184+ for char in self .rfile .read (payload_length ):
154185 if isinstance (char , str ): # python2 fix
155186 char = ord (char )
156187 char ^= masks [len (decoded ) % 4 ]
@@ -164,29 +195,10 @@ def send_message(self, message):
164195
165196 def send_text (self , message ):
166197 '''
167- +-+-+-+-+-------+-+-------------+-------------------------------+
168- 0 1 2 3
169- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
170- +-+-+-+-+-------+-+-------------+-------------------------------+
171- |F|R|R|R| opcode|M| Payload len | Extended payload length |
172- |I|S|S|S| (4) |A| (7) | (16/64) |
173- |N|V|V|V| |S| | (if payload len==126/127) |
174- | |1|2|3| |K| | |
175- +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
176- | Extended payload length continued, if payload len == 127 |
177- + - - - - - - - - - - - - - - - +-------------------------------+
178- | Payload Data continued ... |
179- +---------------------------------------------------------------+
180-
181198 NOTES
182199 Fragmented(=continuation) messages are not being used since their usage
183200 is needed in very limited cases - when we don't know the payload length.
184201 '''
185-
186- FIN = 0x80
187- OPCODE_TEXT = 0x01
188- EXT_PAYLOAD_16BITS = 0x7e
189- EXT_PAYLOAD_64BITS = 0x7f
190202
191203 # Validate message
192204 if isinstance (message , bytes ):
@@ -212,13 +224,13 @@ def send_text(self, message):
212224 # Extended payload
213225 elif payload_length >= 126 and payload_length <= 65535 :
214226 header .append (FIN | OPCODE_TEXT )
215- header .append (EXT_PAYLOAD_16BITS )
227+ header .append (PAYLOAD_LEN_EXT16 )
216228 header .extend (struct .pack (">H" , payload_length ))
217229
218230 # Huge extended payload
219231 elif payload_length < 18446744073709551616 :
220232 header .append (FIN | OPCODE_TEXT )
221- header .append (EXT_PAYLOAD_64BITS )
233+ header .append (PAYLOAD_LEN_EXT64 )
222234 header .extend (struct .pack (">Q" , payload_length ))
223235
224236 else :
0 commit comments