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 """Base stream class. 32 """ 33 34 35 # Note: request.connection.write/read are used in this module, even though 36 # mod_python document says that they should be used only in connection 37 # handlers. Unfortunately, we have no other options. For example, 38 # request.write/read are not suitable because they don't allow direct raw bytes 39 # writing/reading. 40 41 42 import socket 43 44 from mod_pywebsocket import util 45 46 47 # Exceptions 48 49 50 class ConnectionTerminatedException(Exception): 51 """This exception will be raised when a connection is terminated 52 unexpectedly. 53 """ 54 55 pass 56 57 58 class InvalidFrameException(ConnectionTerminatedException): 59 """This exception will be raised when we received an invalid frame we 60 cannot parse. 61 """ 62 63 pass 64 65 66 class BadOperationException(Exception): 67 """This exception will be raised when send_message() is called on 68 server-terminated connection or receive_message() is called on 69 client-terminated connection. 70 """ 71 72 pass 73 74 75 class UnsupportedFrameException(Exception): 76 """This exception will be raised when we receive a frame with flag, opcode 77 we cannot handle. Handlers can just catch and ignore this exception and 78 call receive_message() again to continue processing the next frame. 79 """ 80 81 pass 82 83 84 class InvalidUTF8Exception(Exception): 85 """This exception will be raised when we receive a text frame which 86 contains invalid UTF-8 strings. 87 """ 88 89 pass 90 91 92 class StreamBase(object): 93 """Base stream class.""" 94 95 def __init__(self, request): 96 """Construct an instance. 97 98 Args: 99 request: mod_python request. 100 """ 101 102 self._logger = util.get_class_logger(self) 103 104 self._request = request 105 106 def _read(self, length): 107 """Reads length bytes from connection. In case we catch any exception, 108 prepends remote address to the exception message and raise again. 109 110 Raises: 111 ConnectionTerminatedException: when read returns empty string. 112 """ 113 114 try: 115 read_bytes = self._request.connection.read(length) 116 if not read_bytes: 117 raise ConnectionTerminatedException( 118 'Receiving %d byte failed. Peer (%r) closed connection' % 119 (length, (self._request.connection.remote_addr,))) 120 return read_bytes 121 except socket.error, e: 122 # Catch a socket.error. Because it's not a child class of the 123 # IOError prior to Python 2.6, we cannot omit this except clause. 124 # Use %s rather than %r for the exception to use human friendly 125 # format. 126 raise ConnectionTerminatedException( 127 'Receiving %d byte failed. socket.error (%s) occurred' % 128 (length, e)) 129 except IOError, e: 130 # Also catch an IOError because mod_python throws it. 131 raise ConnectionTerminatedException( 132 'Receiving %d byte failed. IOError (%s) occurred' % 133 (length, e)) 134 135 def _write(self, bytes_to_write): 136 """Writes given bytes to connection. In case we catch any exception, 137 prepends remote address to the exception message and raise again. 138 """ 139 140 try: 141 self._request.connection.write(bytes_to_write) 142 except Exception, e: 143 util.prepend_message_to_exception( 144 'Failed to send message to %r: ' % 145 (self._request.connection.remote_addr,), 146 e) 147 raise 148 149 def receive_bytes(self, length): 150 """Receives multiple bytes. Retries read when we couldn't receive the 151 specified amount. 152 153 Raises: 154 ConnectionTerminatedException: when read returns empty string. 155 """ 156 157 read_bytes = [] 158 while length > 0: 159 new_read_bytes = self._read(length) 160 read_bytes.append(new_read_bytes) 161 length -= len(new_read_bytes) 162 return ''.join(read_bytes) 163 164 def _read_until(self, delim_char): 165 """Reads bytes until we encounter delim_char. The result will not 166 contain delim_char. 167 168 Raises: 169 ConnectionTerminatedException: when read returns empty string. 170 """ 171 172 read_bytes = [] 173 while True: 174 ch = self._read(1) 175 if ch == delim_char: 176 break 177 read_bytes.append(ch) 178 return ''.join(read_bytes) 179 180 181 # vi:sts=4 sw=4 et 182