Added timeout to smtplib (to SMTP and SMTP_SSL). Also created

the test_smtplib.py file, with a basic test and the timeout
ones. Docs are updated too.
This commit is contained in:
Facundo Batista 2007-03-28 18:25:54 +00:00
parent 1fe9f968a2
commit 366d6262f8
3 changed files with 89 additions and 27 deletions

View File

@ -15,13 +15,16 @@ listener daemon. For details of SMTP and ESMTP operation, consult
(\citetitle{SMTP Service Extensions}).
\begin{classdesc}{SMTP}{\optional{host\optional{, port\optional{,
local_hostname}}}}
local_hostname\optional{, timeout}}}}}
A \class{SMTP} instance encapsulates an SMTP connection. It has
methods that support a full repertoire of SMTP and ESMTP
operations. If the optional host and port parameters are given, the
SMTP \method{connect()} method is called with those parameters during
initialization. An \exception{SMTPConnectError} is raised if the
specified host doesn't respond correctly.
The optional \var{timeout} parameter specifies a timeout in seconds for the
connection attempt (if not specified, or passed as None, the global
default timeout setting will be used).
For normal use, you should only require the initialization/connect,
\method{sendmail()}, and \method{quit()} methods. An example is
@ -31,7 +34,7 @@ included below.
\begin{classdesc}{SMTP_SSL}{\optional{host\optional{, port\optional{,
local_hostname\optional{,
keyfile\optional{,
certfile}}}}}}
certfile\optional{, timeout}}}}}}}
A \class{SMTP_SSL} instance behaves exactly the same as instances of \class{SMTP}.
\class{SMTP_SSL} should be used for situations where SSL is required from
the beginning of the connection and using \method{starttls()} is not appropriate.
@ -39,6 +42,9 @@ If \var{host} is not specified, the local host is used. If \var{port} is
omitted, the standard SMTP-over-SSL port (465) is used. \var{keyfile} and \var{certfile}
are also optional, and can contain a PEM formatted private key and
certificate chain file for the SSL connection.
The optional \var{timeout} parameter specifies a timeout in seconds for the
connection attempt (if not specified, or passed as None, the global
default timeout setting will be used).
\end{classdesc}
\begin{classdesc}{LMTP}{\optional{host\optional{, port\optional{,

View File

@ -230,7 +230,7 @@ class SMTP:
ehlo_resp = None
does_esmtp = 0
def __init__(self, host = '', port = 0, local_hostname = None):
def __init__(self, host='', port=0, local_hostname=None, timeout=None):
"""Initialize a new instance.
If specified, `host' is the name of the remote host to which to
@ -241,6 +241,7 @@ class SMTP:
the local hostname is found using socket.getfqdn().
"""
self.timeout = timeout
self.esmtp_features = {}
self.default_port = SMTP_PORT
if host:
@ -274,12 +275,11 @@ class SMTP:
"""
self.debuglevel = debuglevel
def _get_socket(self,af, socktype, proto,sa):
def _get_socket(self, port, host, timeout):
# This makes it simpler for SMTP_SSL to use the SMTP connect code
# and just alter the socket connection bit.
self.sock = socket.socket(af, socktype, proto)
if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
self.sock.connect(sa)
return socket.create_connection((port, host), timeout)
def connect(self, host='localhost', port = 0):
"""Connect to a host on a given port.
@ -301,21 +301,7 @@ class SMTP:
raise socket.error, "nonnumeric port"
if not port: port = self.default_port
if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
msg = "getaddrinfo returns an empty list"
self.sock = None
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
try:
self._get_socket(af,socktype,proto,sa)
except socket.error, msg:
if self.debuglevel > 0: print>>stderr, 'connect fail:', msg
if self.sock:
self.sock.close()
self.sock = None
continue
break
if not self.sock:
raise socket.error, msg
self.sock = self._get_socket(host, port, self.timeout)
(code, msg) = self.getreply()
if self.debuglevel > 0: print>>stderr, "connect:", msg
return (code, msg)
@ -733,16 +719,15 @@ class SMTP_SSL(SMTP):
certificate chain file for the SSL connection.
"""
def __init__(self, host='', port=0, local_hostname=None,
keyfile = None, certfile = None):
keyfile=None, certfile=None, timeout=None):
self.keyfile = keyfile
self.certfile = certfile
SMTP.__init__(self,host,port,local_hostname)
SMTP.__init__(self, host, port, local_hostname, timeout)
self.default_port = SMTP_SSL_PORT
def _get_socket(self,af, socktype, proto,sa):
self.sock = socket.socket(af, socktype, proto)
def _get_socket(self, host, port, timeout):
if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
self.sock.connect(sa)
self.sock = socket.create_connection((host, port), timeout)
sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
self.sock = SSLFakeSocket(self.sock, sslobj)
self.file = SSLFakeFile(sslobj)

71
Lib/test/test_smtplib.py Normal file
View File

@ -0,0 +1,71 @@
import socket
import threading
import smtplib
import time
from unittest import TestCase
from test import test_support
def server(evt):
serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serv.settimeout(3)
serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serv.bind(("", 9091))
serv.listen(5)
try:
conn, addr = serv.accept()
except socket.timeout:
pass
else:
conn.send("220 Hola mundo\n")
conn.close()
finally:
serv.close()
evt.set()
class GeneralTests(TestCase):
def setUp(self):
self.evt = threading.Event()
threading.Thread(target=server, args=(self.evt,)).start()
time.sleep(.1)
def tearDown(self):
self.evt.wait()
def testBasic(self):
# connects
smtp = smtplib.SMTP("localhost", 9091)
smtp.sock.close()
def testTimeoutDefault(self):
# default
smtp = smtplib.SMTP("localhost", 9091)
self.assertTrue(smtp.sock.gettimeout() is None)
smtp.sock.close()
def testTimeoutValue(self):
# a value
smtp = smtplib.SMTP("localhost", 9091, timeout=30)
self.assertEqual(smtp.sock.gettimeout(), 30)
smtp.sock.close()
def testTimeoutNone(self):
# None, having other default
previous = socket.getdefaulttimeout()
socket.setdefaulttimeout(30)
try:
smtp = smtplib.SMTP("localhost", 9091, timeout=None)
finally:
socket.setdefaulttimeout(previous)
self.assertEqual(smtp.sock.gettimeout(), 30)
smtp.sock.close()
def test_main(verbose=None):
test_support.run_unittest(GeneralTests)
if __name__ == '__main__':
test_main()