1
1
# -*- coding: utf-8 -*-
2
2
import logging
3
3
import socket
4
+ import ssl
4
5
import time
5
6
import threading
6
7
import types
7
8
9
+ try :
10
+ from OpenSSL .SSL import Error as pyOpenSSLError
11
+ except ImportError :
12
+ class pyOpenSSLError (Exception ):
13
+ pass
14
+
8
15
from ws4py import WS_KEY , WS_VERSION
9
16
from ws4py .exc import HandshakeError , StreamClosed
10
17
from ws4py .streaming import Stream
@@ -99,7 +106,12 @@ def __init__(self, sock, protocols=None, extensions=None, environ=None, heartbea
99
106
"""
100
107
Underlying connection.
101
108
"""
102
-
109
+
110
+ self ._is_secure = hasattr (sock , '_ssl' ) or hasattr (sock , '_sslobj' )
111
+ """
112
+ Tell us if the socket is secure or not.
113
+ """
114
+
103
115
self .client_terminated = False
104
116
"""
105
117
Indicates if the client has been marked as terminated.
@@ -301,6 +313,50 @@ def send(self, payload, binary=False):
301
313
else :
302
314
raise ValueError ("Unsupported type '%s' passed to send()" % type (payload ))
303
315
316
+ def _get_from_pending (self ):
317
+ """
318
+ The SSL socket object provides the same interface
319
+ as the socket interface but behaves differently.
320
+
321
+ When data is sent over a SSL connection
322
+ more data may be read than was requested from by
323
+ the ws4py websocket object.
324
+
325
+ In that case, the data may have been indeed read
326
+ from the underlying real socket, but not read by the
327
+ application which will expect another trigger from the
328
+ manager's polling mechanism as if more data was still on the
329
+ wire. This will happen only when new data is
330
+ sent by the other peer which means there will be
331
+ some delay before the initial read data is handled
332
+ by the application.
333
+
334
+ Due to this, we have to rely on a non-public method
335
+ to query the internal SSL socket buffer if it has indeed
336
+ more data pending in its buffer.
337
+
338
+ Now, some people in the Python community
339
+ `discourage <https://bugs.python.org/issue21430>`_
340
+ this usage of the ``pending()`` method because it's not
341
+ the right way of dealing with such use case. They advise
342
+ `this approach <https://docs.python.org/dev/library/ssl.html#notes-on-non-blocking-sockets>`_
343
+ instead. Unfortunately, this applies only if the
344
+ application can directly control the poller which is not
345
+ the case with the WebSocket abstraction here.
346
+
347
+ We therefore rely on this `technic <http://stackoverflow.com/questions/3187565/select-and-ssl-in-python>`_
348
+ which seems to be valid anyway.
349
+
350
+ This is a bit of a shame because we have to process
351
+ more data than what wanted initially.
352
+ """
353
+ data = b""
354
+ pending = self .sock .pending ()
355
+ while pending :
356
+ data += self .sock .recv (pending )
357
+ pending = self .sock .pending ()
358
+ return data
359
+
304
360
def once (self ):
305
361
"""
306
362
Performs the operation of reading from the underlying
@@ -322,7 +378,10 @@ def once(self):
322
378
323
379
try :
324
380
b = self .sock .recv (self .reading_buffer_size )
325
- except (socket .error , OSError ) as e :
381
+ # This will only make sense with secure sockets.
382
+ if self ._is_secure :
383
+ b += self ._get_from_pending ()
384
+ except (socket .error , OSError , pyOpenSSLError ) as e :
326
385
self .unhandled_error (e )
327
386
return False
328
387
else :
0 commit comments