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"""Message related utilities. 30 31Note: request.connection.write/read are used in this module, even though 32mod_python document says that they should be used only in connection 33handlers. Unfortunately, we have no other options. For example, 34request.write/read are not suitable because they don't allow direct raw 35bytes writing/reading. 36""" 37 38from __future__ import absolute_import 39import six.moves.queue 40import threading 41 42# Export Exception symbols from msgutil for backward compatibility 43from mod_pywebsocket._stream_exceptions import ConnectionTerminatedException 44from mod_pywebsocket._stream_exceptions import InvalidFrameException 45from mod_pywebsocket._stream_exceptions import BadOperationException 46from mod_pywebsocket._stream_exceptions import UnsupportedFrameException 47 48 49# An API for handler to send/receive WebSocket messages. 50def close_connection(request): 51 """Close connection. 52 53 Args: 54 request: mod_python request. 55 """ 56 request.ws_stream.close_connection() 57 58 59def send_message(request, payload_data, end=True, binary=False): 60 """Send a message (or part of a message). 61 62 Args: 63 request: mod_python request. 64 payload_data: unicode text or str binary to send. 65 end: True to terminate a message. 66 False to send payload_data as part of a message that is to be 67 terminated by next or later send_message call with end=True. 68 binary: send payload_data as binary frame(s). 69 Raises: 70 BadOperationException: when server already terminated. 71 """ 72 request.ws_stream.send_message(payload_data, end, binary) 73 74 75def receive_message(request): 76 """Receive a WebSocket frame and return its payload as a text in 77 unicode or a binary in str. 78 79 Args: 80 request: mod_python request. 81 Raises: 82 InvalidFrameException: when client send invalid frame. 83 UnsupportedFrameException: when client send unsupported frame e.g. some 84 of reserved bit is set but no extension can 85 recognize it. 86 InvalidUTF8Exception: when client send a text frame containing any 87 invalid UTF-8 string. 88 ConnectionTerminatedException: when the connection is closed 89 unexpectedly. 90 BadOperationException: when client already terminated. 91 """ 92 return request.ws_stream.receive_message() 93 94 95def send_ping(request, body): 96 request.ws_stream.send_ping(body) 97 98 99class MessageReceiver(threading.Thread): 100 """This class receives messages from the client. 101 102 This class provides three ways to receive messages: blocking, 103 non-blocking, and via callback. Callback has the highest precedence. 104 105 Note: This class should not be used with the standalone server for wss 106 because pyOpenSSL used by the server raises a fatal error if the socket 107 is accessed from multiple threads. 108 """ 109 def __init__(self, request, onmessage=None): 110 """Construct an instance. 111 112 Args: 113 request: mod_python request. 114 onmessage: a function to be called when a message is received. 115 May be None. If not None, the function is called on 116 another thread. In that case, MessageReceiver.receive 117 and MessageReceiver.receive_nowait are useless 118 because they will never return any messages. 119 """ 120 121 threading.Thread.__init__(self) 122 self._request = request 123 self._queue = six.moves.queue.Queue() 124 self._onmessage = onmessage 125 self._stop_requested = False 126 self.setDaemon(True) 127 self.start() 128 129 def run(self): 130 try: 131 while not self._stop_requested: 132 message = receive_message(self._request) 133 if self._onmessage: 134 self._onmessage(message) 135 else: 136 self._queue.put(message) 137 finally: 138 close_connection(self._request) 139 140 def receive(self): 141 """ Receive a message from the channel, blocking. 142 143 Returns: 144 message as a unicode string. 145 """ 146 return self._queue.get() 147 148 def receive_nowait(self): 149 """ Receive a message from the channel, non-blocking. 150 151 Returns: 152 message as a unicode string if available. None otherwise. 153 """ 154 try: 155 message = self._queue.get_nowait() 156 except six.moves.queue.Empty: 157 message = None 158 return message 159 160 def stop(self): 161 """Request to stop this instance. 162 163 The instance will be stopped after receiving the next message. 164 This method may not be very useful, but there is no clean way 165 in Python to forcefully stop a running thread. 166 """ 167 self._stop_requested = True 168 169 170class MessageSender(threading.Thread): 171 """This class sends messages to the client. 172 173 This class provides both synchronous and asynchronous ways to send 174 messages. 175 176 Note: This class should not be used with the standalone server for wss 177 because pyOpenSSL used by the server raises a fatal error if the socket 178 is accessed from multiple threads. 179 """ 180 def __init__(self, request): 181 """Construct an instance. 182 183 Args: 184 request: mod_python request. 185 """ 186 threading.Thread.__init__(self) 187 self._request = request 188 self._queue = six.moves.queue.Queue() 189 self.setDaemon(True) 190 self.start() 191 192 def run(self): 193 while True: 194 message, condition = self._queue.get() 195 condition.acquire() 196 send_message(self._request, message) 197 condition.notify() 198 condition.release() 199 200 def send(self, message): 201 """Send a message, blocking.""" 202 203 condition = threading.Condition() 204 condition.acquire() 205 self._queue.put((message, condition)) 206 condition.wait() 207 208 def send_nowait(self, message): 209 """Send a message, non-blocking.""" 210 211 self._queue.put((message, threading.Condition())) 212 213 214# vi:sts=4 sw=4 et 215