def create_reader(bytes): b = Buffer(1024) for byte in bytes: b.write_byte(byte) b.flip() p = PacketReader(b) p.packet.position = b.position p.packet.limit = b.limit return p
def __init__(self): self.state = self.STATE_INIT self.buffer = Buffer(1024 * 16) self.socket = None self.reader = None self.writer = None self._time_command = False #whether to keep timing stats on a cmd self._command_time = -1 self._incommand = False self.current_resultset = None
class Connection(object): """Represents a single connection to a MySQL Database host.""" STATE_ERROR = -1 STATE_INIT = 0 STATE_CONNECTING = 1 STATE_CONNECTED = 2 STATE_CLOSING = 3 STATE_CLOSED = 4 def __init__(self): self.state = self.STATE_INIT self.buffer = Buffer(1024 * 16) self.socket = None self.reader = None self.writer = None self._time_command = False #whether to keep timing stats on a cmd self._command_time = -1 self._incommand = False self.current_resultset = None def _scramble(self, password, seed): """taken from java jdbc driver, scrambles the password using the given seed according to the mysql login protocol""" stage1 = SHA(password).digest() stage2 = SHA(stage1).digest() md = SHA() md.update(seed) md.update(stage2) #i love python :-): return ''.join(map(chr, [x ^ ord(stage1[i]) for i, x in enumerate(map(ord, md.digest()))])) def _handshake(self, user, password, database, charset): """performs the mysql login handshake""" #init buffer for reading (both pos and lim = 0) self.buffer.clear() self.buffer.flip() #read server welcome packet = self.reader.read_packet() self.protocol_version = packet.read_byte() #normally this would be 10 (0xa) if self.protocol_version == 0xff: #error on initial greeting, possibly too many connection error raise ClientLoginError.from_error_packet(packet, skip = 2) elif self.protocol_version == 0xa: pass #expected else: assert False, "Unexpected protocol version %02x" % self.protocol_version self.server_version = packet.read_bytes_until(0) packet.skip(4) #thread_id scramble_buff = packet.read_bytes(8) packet.skip(1) #filler server_caps = packet.read_short() #CAPS.dbg(server_caps) if not server_caps & CAPS.PROTOCOL_41: assert False, "<4.1 auth not supported" server_language = packet.read_byte() server_status = packet.read_short() packet.skip(13) #filler if packet.remaining: scramble_buff += packet.read_bytes_until(0) else: assert False, "<4.1 auth not supported" client_caps = server_caps #always turn off compression client_caps &= ~CAPS.COMPRESS client_caps &= ~CAPS.NO_SCHEMA #always turn off ssl client_caps &= ~CAPS.SSL if not server_caps & CAPS.CONNECT_WITH_DB and database: assert False, "initial db given but not supported by server" if server_caps & CAPS.CONNECT_WITH_DB and not database: client_caps &= ~CAPS.CONNECT_WITH_DB #build and write our answer to the initial handshake packet self.writer.clear() self.writer.start() self.writer.write_int(client_caps) self.writer.write_int(1024 * 1024 * 32) #16mb max packet if charset: self.writer.write_byte(charset_map[charset.replace("-", "")]) else: self.writer.write_byte(server_language) self.writer.write_bytes('\0' * 23) #filler self.writer.write_bytes(user + '\0') if password: self.writer.write_byte(20) self.writer.write_bytes(self._scramble(password, scramble_buff)) else: self.writer.write_byte(0) if database: self.writer.write_bytes(database + '\0') self.writer.finish(1) self.writer.flush() #read final answer from server self.buffer.flip() packet = self.reader.read_packet() result = packet.read_byte() if result == 0xff: raise ClientLoginError.from_error_packet(packet) elif result == 0xfe: assert False, "old password handshake not implemented" def _close_current_resultset(self, resultset): assert resultset == self.current_resultset self.current_resultset = None def _send_command(self, cmd, cmd_text): """sends a command with the given text""" #self.log.debug('cmd %s %s', cmd, cmd_text) #note: we are not using normal writer.start/finish here, because the cmd #could not fit in buffer, causing flushes in write_string, in that case 'finish' would #not be able to go back to the header of the packet to write the length in that case self.writer.clear() self.writer.write_header(len(cmd_text) + 1 + 4, 0) #1 is len of cmd, 4 is len of header, 0 is packet number self.writer.write_byte(cmd) self.writer.write_bytes(cmd_text) self.writer.flush() def _close(self): #self.log.debug("close mysql client %s", id(self)) try: self.state = self.STATE_CLOSING if self.current_resultset: self.current_resultset.close(True) self.socket.close() self.state = self.STATE_CLOSED except: self.state = self.STATE_ERROR raise def connect(self, host = "localhost", port = 3306, user = "", password = "", db = "", autocommit = None, charset = None, use_unicode=False): """connects to the given host and port with user and password""" #self.log.debug("connect mysql client %s %s %s %s %s", id(self), host, port, user, password) try: #parse addresses of form str <host:port> assert type(host) == str, "make sure host is a string" if host[0] == '/': #assume unix domain socket addr = host elif ':' in host: host, port = host.split(':') port = int(port) addr = (host, port) else: addr = (host, port) assert self.state == self.STATE_INIT, "make sure connection is not already connected or closed" self.state = self.STATE_CONNECTING self.socket = socket.create_connection(addr) self.reader = BufferedPacketReader(self.socket, self.buffer) self.writer = BufferedPacketWriter(self.socket, self.buffer) self._handshake(user, password, db, charset) #handshake complete client can now send commands self.state = self.STATE_CONNECTED if autocommit == False: self.set_autocommit(False) elif autocommit == True: self.set_autocommit(True) else: pass #whatever is the default of the db (ON in the case of mysql) if charset is not None: self.set_charset(charset) self.set_use_unicode(use_unicode) return self except gevent.Timeout: self.state = self.STATE_INIT raise except ClientLoginError: self.state = self.STATE_INIT raise except: self.state = self.STATE_ERROR raise def close(self): """close this connection""" assert self.is_connected(), "make sure connection is connected before closing" if self._incommand != False: assert False, "cannot close while still in a command" self._close() def command(self, cmd, cmd_text): """sends a COM_XXX command with the given text and possibly return a resultset (select)""" #print 'command', cmd, repr(cmd_text), type(cmd_text) assert type(cmd_text) == str #as opposed to unicode assert self.is_connected(), "make sure connection is connected before query" if self._incommand != False: assert False, "overlapped commands not supported" if self.current_resultset: assert False, "overlapped commands not supported, pls read prev resultset and close it" try: self._incommand = True if self._time_command: start_time = time.time() self._send_command(cmd, cmd_text) #read result, expect 1 of OK, ERROR or result set header self.buffer.flip() packet = self.reader.read_packet() result = packet.read_byte() #print 'res', result if self._time_command: end_time = time.time() self._command_time = end_time - start_time if result == 0x00: #OK, return (affected rows, last row id) rowcount = self.reader.read_length_coded_binary() lastrowid = self.reader.read_length_coded_binary() return (rowcount, lastrowid) elif result == 0xff: raise ClientCommandError.from_error_packet(packet) else: #result set self.current_resultset = ResultSet(self, result) return self.current_resultset except socket.error, e: (errorcode, errorstring) = e if errorcode in [errno.ECONNABORTED, errno.ECONNREFUSED, errno.ECONNRESET, errno.EPIPE]: self._incommand = False self.close() if sys.platform == "win32": if errorcode in [errno.WSAECONNABORTED]: self._incommand = False self.close() raise finally:
class Connection(object): """Represents a single connection to a MySQL Database host.""" STATE_ERROR = -1 STATE_INIT = 0 STATE_CONNECTING = 1 STATE_CONNECTED = 2 STATE_CLOSING = 3 STATE_CLOSED = 4 def __init__(self): self.state = self.STATE_INIT self.buffer = Buffer(1024 * 16) self.socket = None self.reader = None self.writer = None self._time_command = False #whether to keep timing stats on a cmd self._command_time = -1 self._incommand = False self.current_resultset = None def _scramble(self, password, seed): """taken from java jdbc driver, scrambles the password using the given seed according to the mysql login protocol""" stage1 = SHA(password).digest() stage2 = SHA(stage1).digest() md = SHA() md.update(seed) md.update(stage2) #i love python :-): return ''.join( map(chr, [ x ^ ord(stage1[i]) for i, x in enumerate(map(ord, md.digest())) ])) def _handshake(self, user, password, database, charset): """performs the mysql login handshake""" #init buffer for reading (both pos and lim = 0) self.buffer.clear() self.buffer.flip() #read server welcome packet = self.reader.read_packet() self.protocol_version = packet.read_byte( ) #normally this would be 10 (0xa) if self.protocol_version == 0xff: #error on initial greeting, possibly too many connection error raise ClientLoginError.from_error_packet(packet, skip=2) elif self.protocol_version == 0xa: pass #expected else: assert False, "Unexpected protocol version %02x" % self.protocol_version self.server_version = packet.read_bytes_until(0) packet.skip(4) #thread_id scramble_buff = packet.read_bytes(8) packet.skip(1) #filler server_caps = packet.read_short() #CAPS.dbg(server_caps) if not server_caps & CAPS.PROTOCOL_41: assert False, "<4.1 auth not supported" server_language = packet.read_byte() server_status = packet.read_short() packet.skip(13) #filler if packet.remaining: scramble_buff += packet.read_bytes_until(0) else: assert False, "<4.1 auth not supported" client_caps = server_caps #always turn off compression client_caps &= ~CAPS.COMPRESS client_caps &= ~CAPS.NO_SCHEMA #always turn off ssl client_caps &= ~CAPS.SSL if not server_caps & CAPS.CONNECT_WITH_DB and database: assert False, "initial db given but not supported by server" if server_caps & CAPS.CONNECT_WITH_DB and not database: client_caps &= ~CAPS.CONNECT_WITH_DB #build and write our answer to the initial handshake packet self.writer.clear() self.writer.start() self.writer.write_int(client_caps) self.writer.write_int(1024 * 1024 * 32) #16mb max packet if charset: self.writer.write_byte(charset_map[charset.replace("-", "")]) else: self.writer.write_byte(server_language) self.writer.write_bytes('\0' * 23) #filler self.writer.write_bytes(user + '\0') if password: self.writer.write_byte(20) self.writer.write_bytes(self._scramble(password, scramble_buff)) else: self.writer.write_byte(0) if database: self.writer.write_bytes(database + '\0') self.writer.finish(1) self.writer.flush() #read final answer from server self.buffer.flip() packet = self.reader.read_packet() result = packet.read_byte() if result == 0xff: raise ClientLoginError.from_error_packet(packet) elif result == 0xfe: assert False, "old password handshake not implemented" def _close_current_resultset(self, resultset): assert resultset == self.current_resultset self.current_resultset = None def _send_command(self, cmd, cmd_text): """sends a command with the given text""" #self.log.debug('cmd %s %s', cmd, cmd_text) #note: we are not using normal writer.start/finish here, because the cmd #could not fit in buffer, causing flushes in write_string, in that case 'finish' would #not be able to go back to the header of the packet to write the length in that case self.writer.clear() self.writer.write_header( len(cmd_text) + 1 + 4, 0) #1 is len of cmd, 4 is len of header, 0 is packet number self.writer.write_byte(cmd) self.writer.write_bytes(cmd_text) self.writer.flush() def _close(self): #self.log.debug("close mysql client %s", id(self)) try: self.state = self.STATE_CLOSING if self.current_resultset: self.current_resultset.close(True) self.socket.close() self.state = self.STATE_CLOSED except: self.state = self.STATE_ERROR raise def connect(self, host="localhost", port=3306, user="", password="", db="", autocommit=None, charset=None, use_unicode=False): """connects to the given host and port with user and password""" #self.log.debug("connect mysql client %s %s %s %s %s", id(self), host, port, user, password) try: #parse addresses of form str <host:port> assert type(host) == str, "make sure host is a string" if host[0] == '/': #assume unix domain socket addr = host elif ':' in host: host, port = host.split(':') port = int(port) addr = (host, port) else: addr = (host, port) assert self.state == self.STATE_INIT, "make sure connection is not already connected or closed" self.state = self.STATE_CONNECTING self.socket = socket.create_connection(addr) self.reader = BufferedPacketReader(self.socket, self.buffer) self.writer = BufferedPacketWriter(self.socket, self.buffer) self._handshake(user, password, db, charset) #handshake complete client can now send commands self.state = self.STATE_CONNECTED if autocommit == False: self.set_autocommit(False) elif autocommit == True: self.set_autocommit(True) else: pass #whatever is the default of the db (ON in the case of mysql) if charset is not None: self.set_charset(charset) self.set_use_unicode(use_unicode) return self except gevent.Timeout: self.state = self.STATE_INIT raise except ClientLoginError: self.state = self.STATE_INIT raise except: self.state = self.STATE_ERROR raise def close(self): """close this connection""" assert self.is_connected( ), "make sure connection is connected before closing" if self._incommand != False: assert False, "cannot close while still in a command" self._close() def command(self, cmd, cmd_text): """sends a COM_XXX command with the given text and possibly return a resultset (select)""" #print 'command', cmd, repr(cmd_text), type(cmd_text) assert type(cmd_text) == str #as opposed to unicode assert self.is_connected( ), "make sure connection is connected before query" if self._incommand != False: assert False, "overlapped commands not supported" if self.current_resultset: assert False, "overlapped commands not supported, pls read prev resultset and close it" try: self._incommand = True if self._time_command: start_time = time.time() self._send_command(cmd, cmd_text) #read result, expect 1 of OK, ERROR or result set header self.buffer.flip() packet = self.reader.read_packet() result = packet.read_byte() #print 'res', result if self._time_command: end_time = time.time() self._command_time = end_time - start_time if result == 0x00: #OK, return (affected rows, last row id) rowcount = self.reader.read_length_coded_binary() lastrowid = self.reader.read_length_coded_binary() return (rowcount, lastrowid) elif result == 0xff: raise ClientCommandError.from_error_packet(packet) else: #result set self.current_resultset = ResultSet(self, result) return self.current_resultset except socket.error, e: (errorcode, errorstring) = e if errorcode in [ errno.ECONNABORTED, errno.ECONNREFUSED, errno.ECONNRESET, errno.EPIPE ]: self._incommand = False self.close() if sys.platform == "win32": if errorcode in [errno.WSAECONNABORTED]: self._incommand = False self.close() raise finally: