Created
December 9, 2013 11:48
-
-
Save yszou/7871107 to your computer and use it in GitHub Desktop.
Tornado实现的SMTP客户端
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
'参考smtplib,以tornado的IOStream实现的SMTP客户端' | |
import socket | |
import re | |
import email.utils | |
import tornado.ioloop | |
import tornado.iostream | |
import tornado.gen | |
IL = tornado.ioloop.IOLoop.instance() | |
CRLF = email.utils.CRLF | |
class SMTPClient(object): | |
#helo/ehlo 后面的主机参数 | |
_fqdn = socket.getfqdn() | |
if '.' in _fqdn: | |
HOST_NAME = _fqdn | |
else: | |
# We can't find an fqdn hostname, so use a domain literal | |
_addr = '127.0.0.1' | |
try: | |
_addr = socket.gethostbyname(socket.gethostname()) | |
except socket.gaierror: | |
pass | |
HOST_NAME = '[%s]' % _addr | |
def __init__(self, host, port=25, user='', password='', ssl=False): | |
self.host = host | |
self.port = port | |
self.user = user | |
self.password = password | |
self.ssl = ssl | |
def send_mail(self, sender, to, msg, callback, errback): | |
self.msg = msg | |
self.callback = callback | |
self.errback = errback | |
self.sender = sender | |
self.to = [to] if isinstance(to, basestring) else to | |
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) | |
self.stream = tornado.iostream.IOStream(s) if not self.ssl else tornado.iostream.SSLIOStream(s) | |
self.stream.connect((self.host, self.port), self.session) | |
def quoteaddr(self, addr): | |
"""Quote a subset of the email addresses defined by RFC 821. | |
Should be able to handle anything rfc822.parseaddr can handle. | |
""" | |
m = (None, None) | |
try: | |
m = email.utils.parseaddr(addr)[1] | |
except AttributeError: | |
pass | |
if m == (None, None): # Indicates parse failure or AttributeError | |
# something weird here.. punt -ddm | |
return "<%s>" % addr | |
elif m is None: | |
# the sender wants an empty return address | |
return "<>" | |
else: | |
return "<%s>" % m | |
def quotedata(self, data): | |
"""Quote data for email. | |
Double leading '.', and change Unix newline '\\n', or Mac '\\r' into | |
Internet CRLF end-of-line. | |
""" | |
return re.sub(r'(?m)^\.', '..', | |
re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)) | |
def process(self, data): | |
'解析返回' | |
if data == '': | |
return -1, '' | |
try: | |
msg = data[4:].strip() | |
code = data[:3] | |
except IndexError: | |
return -1, data | |
try: | |
code = int(code) | |
except ValueError: | |
code = -1 | |
return -1, data | |
return code, msg | |
@tornado.gen.engine | |
def session(self): | |
response = [] | |
#连接 | |
while 1: | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
code, msg = self.process(data) | |
response.append((code, msg)) | |
if code != 220: | |
self.stream.close() | |
self.errback(code, msg, response) | |
raise StopIteration | |
if data[3] != '-': | |
break | |
#EHLO / HELO | |
self.stream.write('ehlo %s%s' % (self.__class__.HOST_NAME, CRLF)) | |
response.append(('ehlo', '%s' % self.__class__.HOST_NAME)) | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
code, msg = self.process(data) | |
response.append((code, msg)) | |
if not (200 <= code <= 299): | |
self.stream.write('helo %s%s' % (self.__class__.HOST_NAME, CRLF)) | |
response.append(('helo', '%s' % self.__class__.HOST_NAME)) | |
while 1: | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
code, msg = self.process(data) | |
response.append((code, msg)) | |
if not (200 <= code <= 299): | |
self.stream.write('rset%s' % CRLF); | |
response.append(('rset', '')) | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
self.stream.close() | |
self.errback(code, msg, response) | |
raise StopIteration | |
if data[3] != '-': | |
break | |
#密码 | |
if self.user: | |
self.stream.write('auth login%s' % CRLF) | |
response.append(('auth login', '')) | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
self.stream.write('%s%s' % (self.user.encode('base64').rstrip(), CRLF)) | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
self.stream.write('%s%s' % (self.password.encode('base64').rstrip(), CRLF)) | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
code, msg = self.process(data) | |
response.append((code, msg)) | |
if code != 235: | |
self.stream.write('rset%s' % CRLF); | |
response.append(('rset', '')) | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
self.stream.close() | |
self.errback(code, msg, response) | |
raise StopIteration | |
self.stream.write('mail FROM:%s%s' % (self.quoteaddr(self.sender), CRLF)) | |
response.append(('mail', 'FROM:%s' % self.quoteaddr(self.sender))) | |
while 1: | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
code, msg = self.process(data) | |
response.append((code, msg)) | |
if code != 250: | |
self.stream.write('rset%s' % CRLF); | |
response.append(('rset', '')) | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
self.stream.close() | |
self.errback(code, msg, response) | |
raise StopIteration | |
if data[3] != '-': | |
break | |
#RCPT | |
error = {} | |
for rcpt in self.to: | |
self.stream.write('rcpt TO:%s%s' % (self.quoteaddr(rcpt), CRLF)) | |
response.append(('rcpt', 'TO:%s' % self.quoteaddr(rcpt))) | |
while 1: | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
code, msg = self.process(data) | |
response.append((code, msg)) | |
if (code != 250) and (code != 251): | |
error[rcpt] = (code, msg) | |
if data[3] != '-': | |
break | |
if len(error) == len(self.to): | |
#全部失败 | |
self.stream.write('rset%s' % CRLF); | |
response.append(('rset', '')) | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
self.stream.close() | |
self.errback(code, msg, response) | |
raise StopIteration | |
#DATA | |
self.stream.write('data%s' % CRLF); | |
response.append(('data', '')) | |
while 1: | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
code, msg = self.process(data) | |
response.append((code, msg)) | |
if code != 354: | |
self.stream.write('rset%s' % CRLF); | |
response.append(('rset', '')) | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
self.stream.close() | |
self.errback(code, msg, response) | |
raise StopIteration | |
if data[3] != '-': | |
break | |
if isinstance(self.msg, basestring): | |
data = self.msg | |
elif isinstance(self.msg, unicode): | |
data = self.msg.encode('utf8') | |
else: | |
data = self.msg.as_string() | |
q = self.quotedata(data) | |
if q[-2:] != CRLF: | |
q = q + CRLF | |
q = q + "." + CRLF | |
self.stream.write(q); | |
response.append(('(mail data)', '')) | |
while 1: | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
code, msg = self.process(data) | |
response.append((code, msg)) | |
if code != 250: | |
self.stream.write('rset%s' % CRLF); | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
self.stream.close() | |
self.errback(code, msg, response) | |
raise StopIteration | |
if data[3] != '-': | |
break | |
self.stream.write('quit%s' % CRLF); | |
response.append(('quit', '')) | |
data = yield tornado.gen.Task(self.stream.read_until, CRLF) | |
code, msg = self.process(data) | |
response.append((code, msg)) | |
self.stream.close() | |
self.callback(code, msg, response) | |
raise StopIteration | |
if __name__ == '__main__': | |
from email.mime.text import MIMEText | |
from email.header import make_header | |
msg = MIMEText('中文', _subtype='html', _charset='utf8') | |
msg['Subject'] = make_header([('标题', 'utf8')]) | |
msg['From'] = make_header([('[email protected]>', None)]) | |
msg['To'] = make_header([('邹业盛', 'utf8'), ('<[email protected]>', None)]) | |
msg['Message-ID'] = email.utils.make_msgid() | |
msg['Date'] = email.utils.formatdate() | |
#print msg.as_string() | |
def callback(code, msg, response): | |
print code, msg | |
print response | |
def errback(code, msg, response): | |
print code, msg | |
print response | |
#client = SMTPClient('gmail-smtp-in-v4v6.l.google.com') | |
#client.send_mail('[email protected]', ['[email protected]', '[email protected]'], msg, | |
# callback, errback) | |
client = SMTPClient('smtp.163.com', 465, '[email protected]', '***', True) | |
client = SMTPClient('127.0.0.1', 25, '', '', False) | |
client.send_mail('[email protected]', ['[email protected]'], msg, callback, errback) | |
IL.start() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment