Home | History | Annotate | Download | only in mod_pywebsocket
      1 # Copyright 2011, Google Inc.
      2 # All rights reserved.
      3 #
      4 # Redistribution and use in source and binary forms, with or without
      5 # modification, are permitted provided that the following conditions are
      6 # met:
      7 #
      8 #     * Redistributions of source code must retain the above copyright
      9 # notice, this list of conditions and the following disclaimer.
     10 #     * Redistributions in binary form must reproduce the above
     11 # copyright notice, this list of conditions and the following disclaimer
     12 # in the documentation and/or other materials provided with the
     13 # distribution.
     14 #     * Neither the name of Google Inc. nor the names of its
     15 # contributors may be used to endorse or promote products derived from
     16 # this software without specific prior written permission.
     17 #
     18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29 
     30 
     31 """Message related utilities.
     32 
     33 Note: request.connection.write/read are used in this module, even though
     34 mod_python document says that they should be used only in connection
     35 handlers. Unfortunately, we have no other options. For example,
     36 request.write/read are not suitable because they don't allow direct raw
     37 bytes writing/reading.
     38 """
     39 
     40 
     41 import Queue
     42 import threading
     43 
     44 
     45 # Export Exception symbols from msgutil for backward compatibility
     46 from mod_pywebsocket._stream_base import ConnectionTerminatedException
     47 from mod_pywebsocket._stream_base import InvalidFrameException
     48 from mod_pywebsocket._stream_base import BadOperationException
     49 from mod_pywebsocket._stream_base import UnsupportedFrameException
     50 
     51 
     52 # An API for handler to send/receive WebSocket messages.
     53 def close_connection(request):
     54     """Close connection.
     55 
     56     Args:
     57         request: mod_python request.
     58     """
     59     request.ws_stream.close_connection()
     60 
     61 
     62 def send_message(request, message, end=True, binary=False):
     63     """Send message.
     64 
     65     Args:
     66         request: mod_python request.
     67         message: unicode text or str binary to send.
     68         end: False to send message as a fragment. All messages until the
     69              first call with end=True (inclusive) will be delivered to the
     70              client in separate frames but as one WebSocket message.
     71         binary: send message as binary frame.
     72     Raises:
     73         BadOperationException: when server already terminated.
     74     """
     75     request.ws_stream.send_message(message, end, binary)
     76 
     77 
     78 def receive_message(request):
     79     """Receive a WebSocket frame and return its payload as a text in
     80     unicode or a binary in str.
     81 
     82     Args:
     83         request: mod_python request.
     84     Raises:
     85         InvalidFrameException:     when client send invalid frame.
     86         UnsupportedFrameException: when client send unsupported frame e.g. some
     87                                    of reserved bit is set but no extension can
     88                                    recognize it.
     89         InvalidUTF8Exception:      when client send a text frame containing any
     90                                    invalid UTF-8 string.
     91         ConnectionTerminatedException: when the connection is closed
     92                                    unexpectedly.
     93         BadOperationException:     when client already terminated.
     94     """
     95     return request.ws_stream.receive_message()
     96 
     97 
     98 def send_ping(request, body=''):
     99     request.ws_stream.send_ping(body)
    100 
    101 
    102 class MessageReceiver(threading.Thread):
    103     """This class receives messages from the client.
    104 
    105     This class provides three ways to receive messages: blocking,
    106     non-blocking, and via callback. Callback has the highest precedence.
    107 
    108     Note: This class should not be used with the standalone server for wss
    109     because pyOpenSSL used by the server raises a fatal error if the socket
    110     is accessed from multiple threads.
    111     """
    112 
    113     def __init__(self, request, onmessage=None):
    114         """Construct an instance.
    115 
    116         Args:
    117             request: mod_python request.
    118             onmessage: a function to be called when a message is received.
    119                        May be None. If not None, the function is called on
    120                        another thread. In that case, MessageReceiver.receive
    121                        and MessageReceiver.receive_nowait are useless
    122                        because they will never return any messages.
    123         """
    124 
    125         threading.Thread.__init__(self)
    126         self._request = request
    127         self._queue = Queue.Queue()
    128         self._onmessage = onmessage
    129         self._stop_requested = False
    130         self.setDaemon(True)
    131         self.start()
    132 
    133     def run(self):
    134         try:
    135             while not self._stop_requested:
    136                 message = receive_message(self._request)
    137                 if self._onmessage:
    138                     self._onmessage(message)
    139                 else:
    140                     self._queue.put(message)
    141         finally:
    142             close_connection(self._request)
    143 
    144     def receive(self):
    145         """ Receive a message from the channel, blocking.
    146 
    147         Returns:
    148             message as a unicode string.
    149         """
    150         return self._queue.get()
    151 
    152     def receive_nowait(self):
    153         """ Receive a message from the channel, non-blocking.
    154 
    155         Returns:
    156             message as a unicode string if available. None otherwise.
    157         """
    158         try:
    159             message = self._queue.get_nowait()
    160         except Queue.Empty:
    161             message = None
    162         return message
    163 
    164     def stop(self):
    165         """Request to stop this instance.
    166 
    167         The instance will be stopped after receiving the next message.
    168         This method may not be very useful, but there is no clean way
    169         in Python to forcefully stop a running thread.
    170         """
    171         self._stop_requested = True
    172 
    173 
    174 class MessageSender(threading.Thread):
    175     """This class sends messages to the client.
    176 
    177     This class provides both synchronous and asynchronous ways to send
    178     messages.
    179 
    180     Note: This class should not be used with the standalone server for wss
    181     because pyOpenSSL used by the server raises a fatal error if the socket
    182     is accessed from multiple threads.
    183     """
    184 
    185     def __init__(self, request):
    186         """Construct an instance.
    187 
    188         Args:
    189             request: mod_python request.
    190         """
    191         threading.Thread.__init__(self)
    192         self._request = request
    193         self._queue = Queue.Queue()
    194         self.setDaemon(True)
    195         self.start()
    196 
    197     def run(self):
    198         while True:
    199             message, condition = self._queue.get()
    200             condition.acquire()
    201             send_message(self._request, message)
    202             condition.notify()
    203             condition.release()
    204 
    205     def send(self, message):
    206         """Send a message, blocking."""
    207 
    208         condition = threading.Condition()
    209         condition.acquire()
    210         self._queue.put((message, condition))
    211         condition.wait()
    212 
    213     def send_nowait(self, message):
    214         """Send a message, non-blocking."""
    215 
    216         self._queue.put((message, threading.Condition()))
    217 
    218 
    219 # vi:sts=4 sw=4 et
    220