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