Skip to content

Commit 4bd2ff3

Browse files
committed
refactoring out the non gevent specific code to wsgi specific code
1 parent d642227 commit 4bd2ff3

3 files changed

Lines changed: 268 additions & 267 deletions

File tree

ws4py/server/geventserver.py

Lines changed: 2 additions & 267 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
1-
import base64
2-
from hashlib import sha1
3-
import types
4-
import socket
5-
61
import 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

155
class 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

16069
class 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-
34681
if __name__ == '__main__':
34782
def echo_handler(websocket, environ):
34883
try:

ws4py/server/wsgi/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)