Python try / except issue with SMTPLib
I wrote a simple SMTP client using Python SMTPLib. Just try adding some error handling - especially in this case when the target server to connect is not available (for example, wrong IP address!)
Currently the trace looks like this:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "smtplib_client.py", line 91, in runClient
try:
File "/usr/local/lib/python2.7/smtplib.py", line 251, in __init__
(code, msg) = self.connect(host, port)
File "/usr/local/lib/python2.7/smtplib.py", line 311, in connect
self.sock = self._get_socket(host, port, self.timeout)
File "/usr/local/lib/python2.7/smtplib.py", line 286, in _get_socket
return socket.create_connection((host, port), timeout)
File "/usr/local/lib/python2.7/socket.py", line 571, in create_connection
raise err
socket.error: [Errno 111] Connection refused
It is so clear that this is "create_connection" in socket.py going bang. This has its own try / except block:
for res in getaddrinfo(host, port, 0, SOCK_STREAM):
af, socktype, proto, canonname, sa = res
sock = None
try:
sock = socket(af, socktype, proto)
if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
sock.settimeout(timeout)
if source_address:
sock.bind(source_address)
sock.connect(sa)
return sock
except error as _:
err = _
if sock is not None:
sock.close()
if err is not None:
raise err
else:
raise error("getaddrinfo returns an empty list")
My runClient () function looks like this:
def runClient(configName = 'default', testFile = './test.xml'):
cliCfg = getClientConfig(configName)
print cliCfg.find('logfile').text
# startLogger(cliCfg.find('logfile').text)
clientCert = cliCfg.find('cert').text
clientKey = cliCfg.find('key').text
serverHost = cliCfg.find('serverhost').text
serverPort = int(cliCfg.find('serverport').text)
myMail = MailMessageHandler()
msgSrc = myMail.readMessageSource(testFile)
allMsgs = myMail.processMessages(msgSrc)
inx = 1
for msg in allMsgs:
validMsg = True
requiredKeys = ('ehlo', 'sndr', 'rcpt', 'body')
for msgItems in requiredKeys:
if len(msg[msgItems]) == 0:
validMsg = False
if validMsg:
try:
server = smtplib.SMTP(serverHost, serverPort)
server.ehlo(msg['ehlo'])
thisSender = msg['sndr']
thisRecipient = msg['rcpt']
thisMessage = MIMEText(msg['body'])
thisMessage['To'] = email.utils.formataddr(('', thisRecipient))
thisMessage['From'] = email.utils.formataddr(('', thisSender))
thisMessage['Subject'] = msg['subject']
thisMessage['Message-id'] = email.utils.make_msgid()
now = datetime.now()
day = now.strftime('%a')
date = now.strftime('%d %b %Y %X')
thisMessage['Date'] = day + ', ' + date + ' -0000'
if msg['tls'].lower() == 'true':
server.starttls('certs/client/client.key', 'certs/client/client.crt')
logging.info ("Message: " + thisMessage['Message-id'] + " to be sent over TLS")
server.sendmail(thisSender, thisRecipient.split(","), thisMessage.as_string())
logging.info ("Message: " + thisMessage['Message-id'] + " sent successfully to " + serverHost + ":" + cliCfg.find('serverport').text)
logging.info ("Message: " + thisMessage['Message-id'] + " had sender: " + thisMessage['From'])
logging.info ("Message: " + thisMessage['Message-id'] + " had recipient(s): " + thisMessage['To'])
except socket.error as e:
print "Could not connect to server - is it down? ({0}): {1}".format(e.strerrror)
except:
print "Unknown error:", sys.exc_info()[0]
finally:
server.quit()
else:
print "Improperly formatted source mail - please check"
What I don't get is the trace shows the call raise err
. So it is clear that err
it is not None and so it must be set as part of except error as _:
.... So the error is handled first, but as part of the handler a copy ( err
) is created - which is then hoisted outside the try / except block, so it unprocessed. This unhandled error must then "go through a backup" of the call stack ( get_socket
does not have a try / except block as well as connect
or __init__
), so outside of the try / except block for the original error
in create_connection
, the copy of the error err
must necessarily "cascade" back into the try / except block in my runClient function?
source to share
Following the support of @AndreySobolev, I got the following simple code:
import smtplib, socket
try:
mylib = smtplib.SMTP("127.0.0.1", 25)
except socket.error as e:
print "could not connect"
So I went back to my smtplib_client.py and the block commented out most of the "try" section. This worked great ... so little by little, I was rebuilding the try section more and more ... and every time it worked fine. The final version is shown below. Apart from what I am doing in my handler except socket.error
, I cannot say that I am aware of anything that I changed - furthermore, I have added also server = None
to stop the section finally
. Oh, and I also had to add "socket" to my import list. Without that, I could understand, barring mishandling - but I don't understand why it didn't fire at all, or even generate an "undefined" error .... Odd!
Working code:
def runClient(configName = 'default', testFile = './test.xml'):
cliCfg = getClientConfig(configName)
print cliCfg.find('logfile').text
# startLogger(cliCfg.find('logfile').text)
clientCert = cliCfg.find('cert').text
clientKey = cliCfg.find('key').text
serverHost = cliCfg.find('serverhost').text
serverPort = int(cliCfg.find('serverport').text)
myMail = MailMessageHandler()
msgSrc = myMail.readMessageSource(testFile)
allMsgs = myMail.processMessages(msgSrc)
inx = 1
for msg in allMsgs:
validMsg = True
requiredKeys = ('ehlo', 'sndr', 'rcpt', 'body')
for msgItems in requiredKeys:
if len(msg[msgItems]) == 0:
validMsg = False
if validMsg:
try:
server = None
server = smtplib.SMTP(serverHost, serverPort)
server.ehlo(msg['ehlo'])
thisSender = msg['sndr']
thisRecipient = msg['rcpt']
thisMessage = MIMEText(msg['body'])
thisMessage['To'] = email.utils.formataddr(('', thisRecipient))
thisMessage['From'] = email.utils.formataddr(('', thisSender))
thisMessage['Subject'] = msg['subject']
thisMessage['Message-id'] = email.utils.make_msgid()
now = datetime.now()
day = now.strftime('%a')
date = now.strftime('%d %b %Y %X')
thisMessage['Date'] = day + ', ' + date + ' -0000'
if msg['tls'].lower() == 'true':
server.starttls('certs/client/client.key', 'certs/client/client.crt')
logging.info ("Message: " + thisMessage['Message-id'] + " to be sent over TLS")
server.sendmail(thisSender, thisRecipient.split(","), thisMessage.as_string())
logging.info ("Message: " + thisMessage['Message-id'] + " sent successfully to " + serverHost + ":" + cliCfg.find('serverport').text)
logging.info ("Message: " + thisMessage['Message-id'] + " had sender: " + thisMessage['From'])
logging.info ("Message: " + thisMessage['Message-id'] + " had recipient(s): " + thisMessage['To'])
except socket.error as e:
logging.error ("Could not connect to " + serverHost + ":" + cliCfg.find('serverport').text + " - is it listening / up?")
except:
print "Unknown error:", sys.exc_info()[0]
finally:
if server != None:
server.quit()
else:
print "Improperly formatted source mail - please check"
Confused, but relieved! Thank you, Andrey!
source to share