mirror of
https://github.com/python/cpython.git
synced 2025-08-03 00:23:06 +00:00
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:
parent
1fe9f968a2
commit
366d6262f8
3 changed files with 89 additions and 27 deletions
|
@ -15,13 +15,16 @@ listener daemon. For details of SMTP and ESMTP operation, consult
|
||||||
(\citetitle{SMTP Service Extensions}).
|
(\citetitle{SMTP Service Extensions}).
|
||||||
|
|
||||||
\begin{classdesc}{SMTP}{\optional{host\optional{, port\optional{,
|
\begin{classdesc}{SMTP}{\optional{host\optional{, port\optional{,
|
||||||
local_hostname}}}}
|
local_hostname\optional{, timeout}}}}}
|
||||||
A \class{SMTP} instance encapsulates an SMTP connection. It has
|
A \class{SMTP} instance encapsulates an SMTP connection. It has
|
||||||
methods that support a full repertoire of SMTP and ESMTP
|
methods that support a full repertoire of SMTP and ESMTP
|
||||||
operations. If the optional host and port parameters are given, the
|
operations. If the optional host and port parameters are given, the
|
||||||
SMTP \method{connect()} method is called with those parameters during
|
SMTP \method{connect()} method is called with those parameters during
|
||||||
initialization. An \exception{SMTPConnectError} is raised if the
|
initialization. An \exception{SMTPConnectError} is raised if the
|
||||||
specified host doesn't respond correctly.
|
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,
|
For normal use, you should only require the initialization/connect,
|
||||||
\method{sendmail()}, and \method{quit()} methods. An example is
|
\method{sendmail()}, and \method{quit()} methods. An example is
|
||||||
|
@ -31,7 +34,7 @@ included below.
|
||||||
\begin{classdesc}{SMTP_SSL}{\optional{host\optional{, port\optional{,
|
\begin{classdesc}{SMTP_SSL}{\optional{host\optional{, port\optional{,
|
||||||
local_hostname\optional{,
|
local_hostname\optional{,
|
||||||
keyfile\optional{,
|
keyfile\optional{,
|
||||||
certfile}}}}}}
|
certfile\optional{, timeout}}}}}}}
|
||||||
A \class{SMTP_SSL} instance behaves exactly the same as instances of \class{SMTP}.
|
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
|
\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.
|
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}
|
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
|
are also optional, and can contain a PEM formatted private key and
|
||||||
certificate chain file for the SSL connection.
|
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}
|
\end{classdesc}
|
||||||
|
|
||||||
\begin{classdesc}{LMTP}{\optional{host\optional{, port\optional{,
|
\begin{classdesc}{LMTP}{\optional{host\optional{, port\optional{,
|
||||||
|
|
|
@ -230,7 +230,7 @@ class SMTP:
|
||||||
ehlo_resp = None
|
ehlo_resp = None
|
||||||
does_esmtp = 0
|
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.
|
"""Initialize a new instance.
|
||||||
|
|
||||||
If specified, `host' is the name of the remote host to which to
|
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().
|
the local hostname is found using socket.getfqdn().
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
self.timeout = timeout
|
||||||
self.esmtp_features = {}
|
self.esmtp_features = {}
|
||||||
self.default_port = SMTP_PORT
|
self.default_port = SMTP_PORT
|
||||||
if host:
|
if host:
|
||||||
|
@ -274,12 +275,11 @@ class SMTP:
|
||||||
"""
|
"""
|
||||||
self.debuglevel = debuglevel
|
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
|
# This makes it simpler for SMTP_SSL to use the SMTP connect code
|
||||||
# and just alter the socket connection bit.
|
# and just alter the socket connection bit.
|
||||||
self.sock = socket.socket(af, socktype, proto)
|
|
||||||
if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
|
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):
|
def connect(self, host='localhost', port = 0):
|
||||||
"""Connect to a host on a given port.
|
"""Connect to a host on a given port.
|
||||||
|
@ -301,21 +301,7 @@ class SMTP:
|
||||||
raise socket.error, "nonnumeric port"
|
raise socket.error, "nonnumeric port"
|
||||||
if not port: port = self.default_port
|
if not port: port = self.default_port
|
||||||
if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
|
if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
|
||||||
msg = "getaddrinfo returns an empty list"
|
self.sock = self._get_socket(host, port, self.timeout)
|
||||||
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
|
|
||||||
(code, msg) = self.getreply()
|
(code, msg) = self.getreply()
|
||||||
if self.debuglevel > 0: print>>stderr, "connect:", msg
|
if self.debuglevel > 0: print>>stderr, "connect:", msg
|
||||||
return (code, msg)
|
return (code, msg)
|
||||||
|
@ -732,17 +718,16 @@ class SMTP_SSL(SMTP):
|
||||||
are also optional - they can contain a PEM formatted private key and
|
are also optional - they can contain a PEM formatted private key and
|
||||||
certificate chain file for the SSL connection.
|
certificate chain file for the SSL connection.
|
||||||
"""
|
"""
|
||||||
def __init__(self, host = '', port = 0, local_hostname = None,
|
def __init__(self, host='', port=0, local_hostname=None,
|
||||||
keyfile = None, certfile = None):
|
keyfile=None, certfile=None, timeout=None):
|
||||||
self.keyfile = keyfile
|
self.keyfile = keyfile
|
||||||
self.certfile = certfile
|
self.certfile = certfile
|
||||||
SMTP.__init__(self,host,port,local_hostname)
|
SMTP.__init__(self, host, port, local_hostname, timeout)
|
||||||
self.default_port = SMTP_SSL_PORT
|
self.default_port = SMTP_SSL_PORT
|
||||||
|
|
||||||
def _get_socket(self,af, socktype, proto,sa):
|
def _get_socket(self, host, port, timeout):
|
||||||
self.sock = socket.socket(af, socktype, proto)
|
|
||||||
if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
|
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)
|
sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
|
||||||
self.sock = SSLFakeSocket(self.sock, sslobj)
|
self.sock = SSLFakeSocket(self.sock, sslobj)
|
||||||
self.file = SSLFakeFile(sslobj)
|
self.file = SSLFakeFile(sslobj)
|
||||||
|
|
71
Lib/test/test_smtplib.py
Normal file
71
Lib/test/test_smtplib.py
Normal 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()
|
Loading…
Add table
Add a link
Reference in a new issue