11# -*- coding: utf-8 -*-
22import logging
33import socket
4+ import ssl
45import time
56import threading
67import types
78
9+ try :
10+ from OpenSSL .SSL import Error as pyOpenSSLError
11+ except ImportError :
12+ class pyOpenSSLError (Exception ):
13+ pass
14+
815from ws4py import WS_KEY , WS_VERSION
916from ws4py .exc import HandshakeError , StreamClosed
1017from ws4py .streaming import Stream
@@ -99,7 +106,12 @@ def __init__(self, sock, protocols=None, extensions=None, environ=None, heartbea
99106 """
100107 Underlying connection.
101108 """
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+
103115 self .client_terminated = False
104116 """
105117 Indicates if the client has been marked as terminated.
@@ -301,6 +313,50 @@ def send(self, payload, binary=False):
301313 else :
302314 raise ValueError ("Unsupported type '%s' passed to send()" % type (payload ))
303315
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+
304360 def once (self ):
305361 """
306362 Performs the operation of reading from the underlying
@@ -322,7 +378,10 @@ def once(self):
322378
323379 try :
324380 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 :
326385 self .unhandled_error (e )
327386 return False
328387 else :
0 commit comments