def read(self, length=None): """ Read a line from the socket is no length is specified, otherwise read ``length`` bytes. Always strip away the newlines. """ try: if length is not None: bytes_left = length + 2 # read the line ending if length > self.MAX_READ_LENGTH: # apparently reading more than 1MB or so from a windows # socket can cause MemoryErrors. See: # https://github.com/andymccurdy/redis-py/issues/205 # read smaller chunks at a time to work around this try: buf = StringIO() while bytes_left > 0: read_len = min(bytes_left, self.MAX_READ_LENGTH) buf.write(self._fp.read(read_len)) bytes_left -= read_len buf.seek(0) return buf.read(length) finally: buf.close() return self._fp.read(bytes_left)[:-2] # no length, read a full line return self._fp.readline()[:-2] except (socket.error, socket.timeout), e: raise ConnectionError("Error while reading from socket: %s" % \ (e.args,))
def connect(self): "Connects to the Redis server if not already connected" if self._sock: return try: sock = self._connect() except socket.error, e: raise ConnectionError(self._error_message(e))
def read_response(self): if not self._reader: raise ConnectionError("Socket closed on remote end") response = self._reader.gets() while response is False: try: buffer = self._sock.recv(4096) except (socket.error, socket.timeout), e: raise ConnectionError("Error while reading from socket: %s" % \ (e.args,)) if not buffer: raise ConnectionError("Socket closed on remote end") self._reader.feed(buffer) # proactively, but not conclusively, check if more data is in the # buffer. if the data received doesn't end with \n, there's more. if not buffer.endswith('\n'): continue response = self._reader.gets()
def send(self, command, redis_instance): "Send ``command`` to the Redis server. Return the result." self.connect(redis_instance) try: self._sock.sendall(command) except socket.error, e: if e.args[0] == errno.EPIPE: self.disconnect() raise ConnectionError("Error %s while writing to socket. %s." % \ e.args)
def connect(self, redis_instance): "Connects to the Redis server is not already connected" if self._sock: return try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((self.host, self.port)) except socket.error, e: raise ConnectionError("Error %s connecting to %s:%s. %s." % \ (e.args[0], self.host, self.port, e.args[1]))
def send_packed_command(self, command): "Send an already packed command to the Redis server" if not self._sock: self.connect() try: self._sock.sendall(command) except socket.error, e: self.disconnect() if len(e.args) == 1: _errno, errmsg = 'UNKNOWN', e.args[0] else: _errno, errmsg = e.args raise ConnectionError("Error %s while writing to socket. %s." % \ (_errno, errmsg))
def read(self, length=None): """ Read a line from the socket is length is None, otherwise read ``length`` bytes """ try: if length is not None: return self._fp.read(length) return self._fp.readline() except socket.error, e: self.disconnect() if e.args and e.args[0] == errno.EAGAIN: raise ConnectionError("Error while reading from socket: %s" % \ e.args[1])
def read_response(self): response = self.read() if not response: raise ConnectionError("Socket closed on remote end") byte, response = response[0], response[1:] # server returned an error if byte == '-': if response.startswith('ERR '): response = response[4:] return ResponseError(response) if response.startswith('LOADING '): # If we're loading the dataset into memory, kill the socket # so we re-initialize (and re-SELECT) next time. raise ConnectionError("Redis is loading data into memory") # single value elif byte == '+': return response # int value elif byte == ':': return long(response) # bulk response elif byte == '$': length = int(response) if length == -1: return None response = self.read(length) return response # multi-bulk response elif byte == '*': length = int(response) if length == -1: return None return [self.read_response() for i in xrange(length)] raise InvalidResponse("Protocol Error")
def on_connect(self): "Initialize the connection, authenticate and select a database" self._parser.on_connect(self) # if a password is specified, authenticate if self.password: self.send_command('AUTH', self.password) if self.read_response() != 'OK': raise AuthenticationError('Invalid Password') # if a database is specified, switch to it if self.db: self.send_command('SELECT', self.db) if self.read_response() != 'OK': raise ConnectionError('Invalid Database')
def _parse_response(self, command_name): conn = self.connection response = conn.read().strip() if not response: self.connection.disconnect() raise ConnectionError("Socket closed on remote end") # server returned a null value if response in ('$-1', '*-1'): return None byte, response = response[0], response[1:] # server returned an error if byte == '-': if response.startswith('ERR '): response = response[4:] raise ResponseError(response) # single value elif byte == '+': return response # int value elif byte == ':': return int(response) # bulk response elif byte == '$': length = int(response) if length == -1: return None response = length and conn.read(length) or '' conn.read(2) # read the \r\n delimiter return response # multi-bulk response elif byte == '*': length = int(response) if length == -1: return None return [self._parse_response(command_name) for i in range(length)] raise InvalidResponse("Unknown response type for: %s" % command_name)
def make_connection(self): "Create a new connection" if self._created_connections >= self.max_connections: raise ConnectionError("Too many connections") self._created_connections += 1 return self.connection_class(**self.connection_kwargs)