12da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Copyright 2011, Google Inc.
22da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# All rights reserved.
32da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
42da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Redistribution and use in source and binary forms, with or without
52da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# modification, are permitted provided that the following conditions are
62da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# met:
72da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
82da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#     * Redistributions of source code must retain the above copyright
92da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# notice, this list of conditions and the following disclaimer.
102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#     * Redistributions in binary form must reproduce the above
112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# copyright notice, this list of conditions and the following disclaimer
122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# in the documentation and/or other materials provided with the
132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# distribution.
142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#     * Neither the name of Google Inc. nor the names of its
152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# contributors may be used to endorse or promote products derived from
162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# this software without specific prior written permission.
172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis"""Message related utilities.
322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
332da489cd246702bee5938545b18a6f710ed214bcJamie GennisNote: request.connection.write/read are used in this module, even though
342da489cd246702bee5938545b18a6f710ed214bcJamie Gennismod_python document says that they should be used only in connection
352da489cd246702bee5938545b18a6f710ed214bcJamie Gennishandlers. Unfortunately, we have no other options. For example,
362da489cd246702bee5938545b18a6f710ed214bcJamie Gennisrequest.write/read are not suitable because they don't allow direct raw
372da489cd246702bee5938545b18a6f710ed214bcJamie Gennisbytes writing/reading.
382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis"""
392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
412da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport Queue
422da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport threading
432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Export Exception symbols from msgutil for backward compatibility
462da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket._stream_base import ConnectionTerminatedException
472da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket._stream_base import InvalidFrameException
482da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket._stream_base import BadOperationException
492da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket._stream_base import UnsupportedFrameException
502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# An API for handler to send/receive WebSocket messages.
532da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef close_connection(request):
542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Close connection.
552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        request: mod_python request.
582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    request.ws_stream.close_connection()
602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
622da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef send_message(request, message, end=True, binary=False):
632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Send message.
642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        request: mod_python request.
672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        message: unicode text or str binary to send.
682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        end: False to send message as a fragment. All messages until the
692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis             first call with end=True (inclusive) will be delivered to the
702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis             client in separate frames but as one WebSocket message.
712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        binary: send message as binary frame.
722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Raises:
732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        BadOperationException: when server already terminated.
742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    request.ws_stream.send_message(message, end, binary)
762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
782da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef receive_message(request):
792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Receive a WebSocket frame and return its payload as a text in
802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    unicode or a binary in str.
812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        request: mod_python request.
842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Raises:
852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        InvalidFrameException:     when client send invalid frame.
862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        UnsupportedFrameException: when client send unsupported frame e.g. some
872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                   of reserved bit is set but no extension can
882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                   recognize it.
892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        InvalidUTF8Exception:      when client send a text frame containing any
902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                   invalid UTF-8 string.
912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        ConnectionTerminatedException: when the connection is closed
922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                   unexpectedly.
932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        BadOperationException:     when client already terminated.
942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return request.ws_stream.receive_message()
962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
982da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef send_ping(request, body=''):
992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    request.ws_stream.send_ping(body)
1002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1022da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass MessageReceiver(threading.Thread):
1032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """This class receives messages from the client.
1042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    This class provides three ways to receive messages: blocking,
1062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    non-blocking, and via callback. Callback has the highest precedence.
1072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Note: This class should not be used with the standalone server for wss
1092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    because pyOpenSSL used by the server raises a fatal error if the socket
1102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    is accessed from multiple threads.
1112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    def __init__(self, request, onmessage=None):
1142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        """Construct an instance.
1152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        Args:
1172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            request: mod_python request.
1182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            onmessage: a function to be called when a message is received.
1192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                       May be None. If not None, the function is called on
1202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                       another thread. In that case, MessageReceiver.receive
1212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                       and MessageReceiver.receive_nowait are useless
1222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                       because they will never return any messages.
1232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        """
1242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        threading.Thread.__init__(self)
1262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._request = request
1272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._queue = Queue.Queue()
1282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._onmessage = onmessage
1292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._stop_requested = False
1302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self.setDaemon(True)
1312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self.start()
1322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    def run(self):
1342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        try:
1352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            while not self._stop_requested:
1362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                message = receive_message(self._request)
1372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                if self._onmessage:
1382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                    self._onmessage(message)
1392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                else:
1402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                    self._queue.put(message)
1412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        finally:
1422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            close_connection(self._request)
1432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    def receive(self):
1452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        """ Receive a message from the channel, blocking.
1462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        Returns:
1482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            message as a unicode string.
1492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        """
1502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return self._queue.get()
1512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    def receive_nowait(self):
1532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        """ Receive a message from the channel, non-blocking.
1542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        Returns:
1562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            message as a unicode string if available. None otherwise.
1572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        """
1582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        try:
1592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            message = self._queue.get_nowait()
1602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        except Queue.Empty:
1612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            message = None
1622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return message
1632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    def stop(self):
1652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        """Request to stop this instance.
1662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        The instance will be stopped after receiving the next message.
1682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        This method may not be very useful, but there is no clean way
1692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        in Python to forcefully stop a running thread.
1702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        """
1712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._stop_requested = True
1722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1742da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass MessageSender(threading.Thread):
1752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """This class sends messages to the client.
1762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    This class provides both synchronous and asynchronous ways to send
1782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    messages.
1792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Note: This class should not be used with the standalone server for wss
1812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    because pyOpenSSL used by the server raises a fatal error if the socket
1822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    is accessed from multiple threads.
1832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    def __init__(self, request):
1862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        """Construct an instance.
1872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        Args:
1892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            request: mod_python request.
1902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        """
1912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        threading.Thread.__init__(self)
1922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._request = request
1932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._queue = Queue.Queue()
1942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self.setDaemon(True)
1952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self.start()
1962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    def run(self):
1982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        while True:
1992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            message, condition = self._queue.get()
2002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            condition.acquire()
2012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            send_message(self._request, message)
2022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            condition.notify()
2032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            condition.release()
2042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    def send(self, message):
2062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        """Send a message, blocking."""
2072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        condition = threading.Condition()
2092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        condition.acquire()
2102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._queue.put((message, condition))
2112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        condition.wait()
2122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    def send_nowait(self, message):
2142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        """Send a message, non-blocking."""
2152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._queue.put((message, threading.Condition()))
2172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# vi:sts=4 sw=4 et
220