bdb554de85240b4e1111ff47e6465514adc54966
[WebKit-https.git] / WebKitTools / pywebsocket / mod_pywebsocket / msgutil.py
1 # Copyright 2009, 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 handlers.
35 Unfortunately, we have no other options. For example, request.write/read are
36 not suitable because they don't allow direct raw bytes writing/reading.
37 """
38
39
40 import Queue
41 import threading
42
43
44 def send_message(request, message):
45     """Send message.
46
47     Args:
48         request: mod_python request.
49         message: unicode string to send.
50     """
51
52     request.connection.write('\x00' + message.encode('utf-8') + '\xff')
53
54
55 def receive_message(request):
56     """Receive a Web Socket frame and return its payload as unicode string.
57
58     Args:
59         request: mod_python request.
60     """
61
62     while True:
63         # Read 1 byte.
64         # mp_conn.read will block if no bytes are available.
65         # Timeout is controlled by TimeOut directive of Apache.
66         frame_type_str = request.connection.read(1)
67         frame_type = ord(frame_type_str[0])
68         if (frame_type & 0x80) == 0x80:
69             # The payload length is specified in the frame.
70             # Read and discard.
71             length = _payload_length(request)
72             _receive_bytes(request, length)
73         else:
74             # The payload is delimited with \xff.
75             bytes = _read_until(request, '\xff')
76             message = bytes.decode('utf-8')
77             if frame_type == 0x00:
78                 return message
79             # Discard data of other types.
80
81
82 def _payload_length(request):
83     length = 0
84     while True:
85         b_str = request.connection.read(1)
86         b = ord(b_str[0])
87         length = length * 128 + (b & 0x7f)
88         if (b & 0x80) == 0:
89             break
90     return length
91
92
93 def _receive_bytes(request, length):
94     bytes = []
95     while length > 0:
96         new_bytes = request.connection.read(length)
97         bytes.append(new_bytes)
98         length -= len(new_bytes)
99     return ''.join(bytes)
100
101
102 def _read_until(request, delim_char):
103     bytes = []
104     while True:
105         ch = request.connection.read(1)
106         if ch == delim_char:
107             break
108         bytes.append(ch)
109     return ''.join(bytes)
110
111
112 class MessageReceiver(threading.Thread):
113     """This class receives messages from the client.
114
115     This class provides three ways to receive messages: blocking, non-blocking,
116     and via callback. Callback has the highest precedence.
117
118     Note: This class should not be used with the standalone server for wss
119     because pyOpenSSL used by the server raises a fatal error if the socket
120     is accessed from multiple threads.
121     """
122     def __init__(self, request, onmessage=None):
123         """Construct an instance.
124
125         Args:
126             request: mod_python request.
127             onmessage: a function to be called when a message is received.
128                        May be None. If not None, the function is called on
129                        another thread. In that case, MessageReceiver.receive
130                        and MessageReceiver.receive_nowait are useless because
131                        they will never return any messages.
132         """
133         threading.Thread.__init__(self)
134         self._request = request
135         self._queue = Queue.Queue()
136         self._onmessage = onmessage
137         self._stop_requested = False
138         self.setDaemon(True)
139         self.start()
140
141     def run(self):
142         while not self._stop_requested:
143             message = receive_message(self._request)
144             if self._onmessage:
145                 self._onmessage(message)
146             else:
147                 self._queue.put(message)
148
149     def receive(self):
150         """ Receive a message from the channel, blocking.
151
152         Returns:
153             message as a unicode string.
154         """
155         return self._queue.get()
156
157     def receive_nowait(self):
158         """ Receive a message from the channel, non-blocking.
159
160         Returns:
161             message as a unicode string if available. None otherwise.
162         """
163         try:
164             message = self._queue.get_nowait()
165         except Queue.Empty:
166             message = None
167         return message
168
169     def stop(self):
170         """Request to stop this instance.
171
172         The instance will be stopped after receiving the next message.
173         This method may not be very useful, but there is no clean way
174         in Python to forcefully stop a running thread.
175         """
176         self._stop_requested = True
177
178
179 class MessageSender(threading.Thread):
180     """This class sends messages to the client.
181
182     This class provides both synchronous and asynchronous ways to send
183     messages.
184
185     Note: This class should not be used with the standalone server for wss
186     because pyOpenSSL used by the server raises a fatal error if the socket
187     is accessed from multiple threads.
188     """
189     def __init__(self, request):
190         """Construct an instance.
191
192         Args:
193             request: mod_python request.
194         """
195         threading.Thread.__init__(self)
196         self._request = request
197         self._queue = Queue.Queue()
198         self.setDaemon(True)
199         self.start()
200
201     def run(self):
202         while True:
203             message, condition = self._queue.get()
204             condition.acquire()
205             send_message(self._request, message)
206             condition.notify()
207             condition.release()
208
209     def send(self, message):
210         """Send a message, blocking."""
211
212         condition = threading.Condition()
213         condition.acquire()
214         self._queue.put((message, condition))
215         condition.wait()
216
217     def send_nowait(self, message):
218         """Send a message, non-blocking."""
219
220         self._queue.put((message, threading.Condition()))
221
222
223 # vi:sts=4 sw=4 et