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
33Note: request.connection.write/read are used in this module, even though
34mod_python document says that they should be used only in connection
35handlers. Unfortunately, we have no other options. For example,
36request.write/read are not suitable because they don't allow direct raw
37bytes writing/reading.
38"""
39
40
41import Queue
42import threading
43
44
45# Export Exception symbols from msgutil for backward compatibility
46from mod_pywebsocket._stream_base import ConnectionTerminatedException
47from mod_pywebsocket._stream_base import InvalidFrameException
48from mod_pywebsocket._stream_base import BadOperationException
49from mod_pywebsocket._stream_base import UnsupportedFrameException
50
51
52# An API for handler to send/receive WebSocket messages.
53def close_connection(request):
54    """Close connection.
55
56    Args:
57        request: mod_python request.
58    """
59    request.ws_stream.close_connection()
60
61
62def send_message(request, payload_data, end=True, binary=False):
63    """Send a message (or part of a message).
64
65    Args:
66        request: mod_python request.
67        payload_data: unicode text or str binary to send.
68        end: True to terminate a message.
69             False to send payload_data as part of a message that is to be
70             terminated by next or later send_message call with end=True.
71        binary: send payload_data as binary frame(s).
72    Raises:
73        BadOperationException: when server already terminated.
74    """
75    request.ws_stream.send_message(payload_data, end, binary)
76
77
78def 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
98def send_ping(request, body=''):
99    request.ws_stream.send_ping(body)
100
101
102class 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
174class 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