michael@0: # Copyright 2011, Google Inc. michael@0: # All rights reserved. michael@0: # michael@0: # Redistribution and use in source and binary forms, with or without michael@0: # modification, are permitted provided that the following conditions are michael@0: # met: michael@0: # michael@0: # * Redistributions of source code must retain the above copyright michael@0: # notice, this list of conditions and the following disclaimer. michael@0: # * Redistributions in binary form must reproduce the above michael@0: # copyright notice, this list of conditions and the following disclaimer michael@0: # in the documentation and/or other materials provided with the michael@0: # distribution. michael@0: # * Neither the name of Google Inc. nor the names of its michael@0: # contributors may be used to endorse or promote products derived from michael@0: # this software without specific prior written permission. michael@0: # michael@0: # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: michael@0: """Base stream class. michael@0: """ michael@0: michael@0: michael@0: # Note: request.connection.write/read are used in this module, even though michael@0: # mod_python document says that they should be used only in connection michael@0: # handlers. Unfortunately, we have no other options. For example, michael@0: # request.write/read are not suitable because they don't allow direct raw bytes michael@0: # writing/reading. michael@0: michael@0: michael@0: from mod_pywebsocket import util michael@0: michael@0: michael@0: # Exceptions michael@0: michael@0: michael@0: class ConnectionTerminatedException(Exception): michael@0: """This exception will be raised when a connection is terminated michael@0: unexpectedly. michael@0: """ michael@0: michael@0: pass michael@0: michael@0: michael@0: class InvalidFrameException(ConnectionTerminatedException): michael@0: """This exception will be raised when we received an invalid frame we michael@0: cannot parse. michael@0: """ michael@0: michael@0: pass michael@0: michael@0: michael@0: class BadOperationException(Exception): michael@0: """This exception will be raised when send_message() is called on michael@0: server-terminated connection or receive_message() is called on michael@0: client-terminated connection. michael@0: """ michael@0: michael@0: pass michael@0: michael@0: michael@0: class UnsupportedFrameException(Exception): michael@0: """This exception will be raised when we receive a frame with flag, opcode michael@0: we cannot handle. Handlers can just catch and ignore this exception and michael@0: call receive_message() again to continue processing the next frame. michael@0: """ michael@0: michael@0: pass michael@0: michael@0: michael@0: class InvalidUTF8Exception(Exception): michael@0: """This exception will be raised when we receive a text frame which michael@0: contains invalid UTF-8 strings. michael@0: """ michael@0: michael@0: pass michael@0: michael@0: michael@0: class StreamBase(object): michael@0: """Base stream class.""" michael@0: michael@0: def __init__(self, request): michael@0: """Construct an instance. michael@0: michael@0: Args: michael@0: request: mod_python request. michael@0: """ michael@0: michael@0: self._logger = util.get_class_logger(self) michael@0: michael@0: self._request = request michael@0: michael@0: def _read(self, length): michael@0: """Reads length bytes from connection. In case we catch any exception, michael@0: prepends remote address to the exception message and raise again. michael@0: michael@0: Raises: michael@0: ConnectionTerminatedException: when read returns empty string. michael@0: """ michael@0: michael@0: bytes = self._request.connection.read(length) michael@0: if not bytes: michael@0: raise ConnectionTerminatedException( michael@0: 'Receiving %d byte failed. Peer (%r) closed connection' % michael@0: (length, (self._request.connection.remote_addr,))) michael@0: return bytes michael@0: michael@0: def _write(self, bytes): michael@0: """Writes given bytes to connection. In case we catch any exception, michael@0: prepends remote address to the exception message and raise again. michael@0: """ michael@0: michael@0: try: michael@0: self._request.connection.write(bytes) michael@0: except Exception, e: michael@0: util.prepend_message_to_exception( michael@0: 'Failed to send message to %r: ' % michael@0: (self._request.connection.remote_addr,), michael@0: e) michael@0: raise michael@0: michael@0: def receive_bytes(self, length): michael@0: """Receives multiple bytes. Retries read when we couldn't receive the michael@0: specified amount. michael@0: michael@0: Raises: michael@0: ConnectionTerminatedException: when read returns empty string. michael@0: """ michael@0: michael@0: bytes = [] michael@0: while length > 0: michael@0: new_bytes = self._read(length) michael@0: bytes.append(new_bytes) michael@0: length -= len(new_bytes) michael@0: return ''.join(bytes) michael@0: michael@0: def _read_until(self, delim_char): michael@0: """Reads bytes until we encounter delim_char. The result will not michael@0: contain delim_char. michael@0: michael@0: Raises: michael@0: ConnectionTerminatedException: when read returns empty string. michael@0: """ michael@0: michael@0: bytes = [] michael@0: while True: michael@0: ch = self._read(1) michael@0: if ch == delim_char: michael@0: break michael@0: bytes.append(ch) michael@0: return ''.join(bytes) michael@0: michael@0: michael@0: # vi:sts=4 sw=4 et