1- import base64
2- from hashlib import sha1
3- import types
4- import socket
5-
61import gevent .pywsgi
7- import gevent .coros
8-
9- from ws4py import WS_KEY
10- from ws4py .exc import HandshakeError
11- from ws4py .streaming import Stream
122
13- WS_VERSION = 8
3+ from ws4py . server . wsgi . middleware import WebSocketUpgradeMiddleware
144
155class UpgradableWSGIHandler (gevent .pywsgi .WSGIHandler ):
166 """Upgradable version of gevent.pywsgi.WSGIHandler class
@@ -69,92 +59,11 @@ def start_response_for_upgrade(status, headers, exc_info=None):
6959 if self .code != 101 :
7060 self .process_result ()
7161 finally :
72- if self .code == 101 :
62+ if hasattr ( self , 'code' ) and self .code == 101 :
7363 self .rfile .close () # makes sure we stop processing requests
7464 else :
7565 gevent .pywsgi .WSGIHandler .run_application (self )
7666
77- class WebSocketUpgradeMiddleware (object ):
78- """WSGI middleware for handling WebSocket upgrades"""
79-
80- def __init__ (self , handle , fallback_app = None , protocols = None , extensions = None ,
81- websocket_class = WebSocket ):
82- self .handle = handle
83- self .fallback_app = fallback_app
84- self .protocols = protocols
85- self .extensions = extensions
86- self .websocket_class = websocket_class
87-
88- def __call__ (self , environ , start_response ):
89- # Initial handshake validation
90- try :
91- if environ .get ('upgrade.protocol' ) != 'websocket' :
92- raise HandshakeError ("Upgrade protocol is not websocket" )
93-
94- if environ .get ('REQUEST_METHOD' ) != 'GET' :
95- raise HandshakeError ('Method is not GET' )
96-
97- key = environ .get ('HTTP_SEC_WEBSOCKET_KEY' )
98- if key :
99- ws_key = base64 .b64decode (key )
100- if len (ws_key ) != 16 :
101- raise HandshakeError ("WebSocket key's length is invalid" )
102-
103- version = environ .get ('HTTP_SEC_WEBSOCKET_VERSION' )
104- if version :
105- if version != str (WS_VERSION ):
106- raise HandshakeError ('Unsupported WebSocket version' )
107- else :
108- raise HandshakeError ('WebSocket version required' )
109- except HandshakeError , e :
110- if self .fallback_app :
111- return self .fallback_app (environ , start_response )
112- else :
113- start_response ("400 Bad Handshake" , [])
114- return [str (e )]
115-
116- # Collect supported subprotocols
117- protocols = self .protocols or []
118- subprotocols = environ .get ('HTTP_SEC_WEBSOCKET_PROTOCOL' )
119- if subprotocols :
120- ws_protocols = []
121- for s in subprotocols .split (',' ):
122- s = s .strip ()
123- if s in protocols :
124- ws_protocols .append (s )
125-
126- # Collect supported extensions
127- exts = self .extensions or []
128- extensions = environ .get ('HTTP_SEC_WEBSOCKET_EXTENSIONS' )
129- if extensions :
130- ws_extensions = []
131- for ext in extensions .split (',' ):
132- ext = ext .strip ()
133- if ext in exts :
134- ws_extensions .append (ext )
135-
136- # Build and start the HTTP response
137- headers = [
138- ('Upgrade' , 'websocket' ),
139- ('Connection' , 'Upgrade' ),
140- ('Sec-WebSocket-Version' , str (WS_VERSION )),
141- ('Sec-WebSocket-Accept' , base64 .b64encode (sha1 (key + WS_KEY ).digest ())),
142- ]
143- if ws_protocols :
144- headers .append (('Sec-WebSocket-Protocol' , ', ' .join (ws_protocols )))
145- if ws_extensions :
146- headers .append (('Sec-WebSocket-Extensions' , ',' .join (ws_extensions )))
147-
148- start_response ("101 Switching Protocols" , headers )
149-
150- # Build a websocket object and pass it to the handler
151- self .handle (
152- self .websocket_class (
153- environ .get ('upgrade.socket' ),
154- ws_protocols ,
155- ws_extensions ,
156- environ ),
157- environ )
15867
15968
16069class WebSocketServer (gevent .pywsgi .WSGIServer ):
@@ -169,180 +78,6 @@ def __init__(self, *args, **kwargs):
16978 extensions = extensions )
17079
17180
172- class WebSocket (object ):
173- lock_mechanism = gevent .coros .Semaphore
174-
175- def __init__ (self , sock , protocols , extensions , environ ):
176- self .stream = Stream ()
177-
178- self .protocols = protocols
179- self .extensions = extensions
180- self .environ = environ
181-
182- self .sock = sock
183- self .sock .settimeout (30.0 )
184-
185- self .client_terminated = False
186- self .server_terminated = False
187-
188- self ._lock = self .lock_mechanism ()
189-
190- def close (self , code = 1000 , reason = '' ):
191- """
192- Call this method to initiate the websocket connection
193- closing by sending a close frame to the connected peer.
194-
195- Once this method is called, the server_terminated
196- attribute is set. Calling this method several times is
197- safe as the closing frame will be sent only the first
198- time.
199-
200- @param code: status code describing why the connection is closed
201- @param reason: a human readable message describing why the connection is closed
202- """
203- if not self .server_terminated :
204- self .server_terminated = True
205- self .write_to_connection (self .stream .close (code = code , reason = reason ))
206- self .close_connection ()
207-
208- @property
209- def terminated (self ):
210- """
211- Returns True if both the client and server have been
212- marked as terminated.
213- """
214- return self .client_terminated is True and self .server_terminated is True
215-
216- def write_to_connection (self , bytes ):
217- """
218- Writes the provided bytes to the underlying connection.
219-
220- @param bytes: data tio send out
221- """
222- return self .sock .sendall (bytes )
223-
224- def read_from_connection (self , amount ):
225- """
226- Reads bytes from the underlying connection.
227-
228- @param amount: quantity to read (if possible)
229- """
230- return self .sock .recv (amount )
231-
232- def close_connection (self ):
233- """
234- Shutdowns then closes the underlying connection.
235- """
236- try :
237- self .sock .shutdown (socket .SHUT_RDWR )
238- self .sock .close ()
239- except :
240- pass
241-
242- def send (self , payload , binary = False ):
243- """
244- Sends the given payload out.
245-
246- If payload is some bytes or a bytearray,
247- then it is sent as a single message not fragmented.
248-
249- If payload is a generator, each chunk is sent as part of
250- fragmented message.
251-
252- @param payload: string, bytes, bytearray or a generator
253- @param binary: if set, handles the payload as a binary message
254- """
255- if isinstance (payload , basestring ) or isinstance (payload , bytearray ):
256- if not binary :
257- self .write_to_connection (self .stream .text_message (payload ).single ())
258- else :
259- self .write_to_connection (self .stream .binary_message (payload ).single ())
260-
261- elif type (payload ) == types .GeneratorType :
262- bytes = payload .next ()
263- first = True
264- for chunk in payload :
265- if not binary :
266- self .write_to_connection (self .stream .text_message (bytes ).fragment (first = first ))
267- else :
268- self .write_to_connection (self .stream .binary_message (payload ).fragment (first = first ))
269- bytes = chunk
270- first = False
271- if not binary :
272- self .write_to_connection (self .stream .text_message (bytes ).fragment (last = True ))
273- else :
274- self .write_to_connection (self .stream .text_message (bytes ).fragment (last = True ))
275-
276- def receive (self , message_obj = False ):
277- """
278- Performs the operation of reading from the underlying
279- connection in order to feed the stream of bytes.
280-
281- We start with a small size of two bytes to be read
282- from the connection so that we can quickly parse an
283- incoming frame header. Then the stream indicates
284- whatever size must be read from the connection since
285- it knows the frame payload length.
286-
287- Note that we perform some automatic opererations:
288-
289- * On a closing message, we respond with a closing
290- message and finally close the connection
291- * We respond to pings with pong messages.
292- * Whenever an error is raised by the stream parsing,
293- we initiate the closing of the connection with the
294- appropiate error code.
295- """
296- next_size = 2
297- #try:
298- while not self .terminated :
299- bytes = self .read_from_connection (next_size )
300- if not bytes and next_size > 0 :
301- raise IOError ()
302-
303- message = None
304- with self ._lock :
305- s = self .stream
306- next_size = s .parser .send (bytes )
307-
308- if s .closing is not None :
309- if not self .server_terminated :
310- next_size = 2
311- self .close (s .closing .code , s .closing .reason )
312- else :
313- self .client_terminated = True
314- raise IOError ()
315-
316- elif s .errors :
317- errors = s .errors [:]
318- for error in s .errors :
319- self .close (error .code , error .reason )
320- s .errors .remove (error )
321- raise IOError ()
322-
323- elif s .has_message :
324- if message_obj :
325- message = s .message
326- s .message = None
327- else :
328- message = str (s .message )
329- s .message .data = None
330- s .message = None
331-
332- for ping in s .pings :
333- self .write_to_connection (s .pong (str (ping .data )))
334- s .pings = []
335- s .pongs = []
336-
337- if message is not None :
338- return message
339-
340- #except:
341- # print "".join(traceback.format_exception(*exc_info()))
342- #finally:
343- # self.client_terminated = self.server_terminated = True
344- # self.close_connection()
345-
34681if __name__ == '__main__' :
34782 def echo_handler (websocket , environ ):
34883 try :
0 commit comments