msgutil.py 7.4 KB

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