我需要创建一个可以接收和存储SMTP消息的类,即电子邮件。为此,我根据发布的here示例使用了asyncore
。但是,asyncore.loop()
正在阻止,因此我无法在代码中执行任何其他操作。
所以我想到了使用线程。这是一个示例代码,显示了我的想法:
class MyServer(smtpd.SMTPServer):
# derive from the python server class
def process_message(..):
# overwrite a smtpd.SMTPServer method to be able to handle the received messages
...
self.list_emails.append(this_email)
def get_number_received_emails(self):
"""Return the current number of stored emails"""
return len(self.list_emails)
def start_receiving(self):
"""Start the actual server to listen on port 25"""
self.thread = threading.Thread(target=asyncore.loop)
self.thread.start()
def stop(self):
"""Stop listening now to port 25"""
# close the SMTPserver from itself
self.close()
self.thread.join()
我希望你能得到这张照片。类MyServer
应该能够以非阻塞方式启动和停止侦听端口25,能够在侦听时(或不侦听)查询消息。 start
方法启动asyncore.loop()
侦听器,当收到电子邮件时,该侦听器会附加到内部列表。类似地,stop
方法应该能够按照建议here停止此服务器。
尽管这个代码不能像我期望的那样工作(asyncore似乎永远运行,甚至我调用上面的stop
方法。我提出的error
在stop
内被捕获,但不在包含target
的{{1}}函数中,我不确定我对这个问题的处理是否有意义。对于修复上述代码或提出更加可靠的实现(不使用使用第三方软件)的任何建议都表示赞赏。
答案 0 :(得分:15)
提供的解决方案可能不是最复杂的解决方案,但它运作合理并且已经过测试。
首先,与asyncore.loop()
有关的问题是,在所有asyncore
频道关闭之前它会阻止,因为用户 Wessie 之前在评论中指出。参考前面提到的smtp example,结果是smtpd.SMTPServer
继承自asyncore.dispatcher
(如smtpd documentation所述),它回答了要关闭哪个频道的问题。
因此,可以使用以下更新的示例代码回答原始问题:
class CustomSMTPServer(smtpd.SMTPServer):
# store the emails in any form inside the custom SMTP server
emails = []
# overwrite the method that is used to process the received
# emails, putting them into self.emails for example
def process_message(self, peer, mailfrom, rcpttos, data):
# email processing
class MyReceiver(object):
def start(self):
"""Start the listening service"""
# here I create an instance of the SMTP server, derived from asyncore.dispatcher
self.smtp = CustomSMTPServer(('0.0.0.0', 25), None)
# and here I also start the asyncore loop, listening for SMTP connection, within a thread
# timeout parameter is important, otherwise code will block 30 seconds after the smtp channel has been closed
self.thread = threading.Thread(target=asyncore.loop,kwargs = {'timeout':1} )
self.thread.start()
def stop(self):
"""Stop listening now to port 25"""
# close the SMTPserver to ensure no channels connect to asyncore
self.smtp.close()
# now it is save to wait for the thread to finish, i.e. for asyncore.loop() to exit
self.thread.join()
# now it finally it is possible to use an instance of this class to check for emails or whatever in a non-blocking way
def count(self):
"""Return the number of emails received"""
return len(self.smtp.emails)
def get(self):
"""Return all emails received so far"""
return self.smtp.emails
....
所以最后,我有一个start
和一个stop
方法来启动和停止在非阻塞环境中侦听端口25。
答案 1 :(得分:4)
来自另一个问题asyncore.loop doesn't terminate when there are no more connections
我认为你略微过度思考线程。使用其他问题的代码,您可以通过以下代码片段启动运行asyncore.loop
的新线程:
import threading
loop_thread = threading.Thread(target=asyncore.loop, name="Asyncore Loop")
# If you want to make the thread a daemon
# loop_thread.daemon = True
loop_thread.start()
这将在一个新主题中运行,并将一直持续到所有asyncore
个频道关闭。
答案 2 :(得分:2)
您应该考虑使用Twisted。 http://twistedmatrix.com/trac/browser/trunk/doc/mail/examples/emailserver.tac演示了如何使用可自定义的按发送挂钩设置SMTP服务器。
答案 3 :(得分:0)
Alex的回答是最好的,但是对于我的用例来说还不完整。我想测试SMTP作为单元测试的一部分,这意味着在测试对象中构建伪造的SMTP服务器,并且该服务器不会终止asyncio线程,因此我必须添加一行以将其设置为守护程序线程,以允许其余的线程单元测试完成,而不会阻塞等待该异步线程的加入。我还添加了所有电子邮件数据的完整日志记录,以便可以断言通过SMTP发送的任何内容。
这是我的假SMTP类:
class TestingSMTP(smtpd.SMTPServer):
def __init__(self, *args, **kwargs):
super(TestingSMTP, self).__init__(*args, **kwargs)
self.emails = []
def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
msg = {'peer': peer,
'mailfrom': mailfrom,
'rcpttos': rcpttos,
'data': data}
msg.update(kwargs)
self.emails.append(msg)
class TestingSMTP_Server(object):
def __init__(self):
self.smtp = TestingSMTP(('0.0.0.0', 25), None)
self.thread = threading.Thread()
def start(self):
self.thread = threading.Thread(target=asyncore.loop, kwargs={'timeout': 1})
self.thread.daemon = True
self.thread.start()
def stop(self):
self.smtp.close()
self.thread.join()
def count(self):
return len(self.smtp.emails)
def get(self):
return self.smtp.emails
这是unittest类如何调用它:
smtp_server = TestingSMTP_Server()
smtp_server.start()
# send some emails
assertTrue(smtp_server.count() == 1) # or however many you intended to send
assertEqual(self.smtp_server.get()[0]['mailfrom'], 'first@fromaddress.com')
# stop it when done testing
smtp_server.stop()
答案 4 :(得分:0)
万一其他人需要更多这些东西,这就是我最终使用的东西。这将smtpd用于电子邮件服务器,将smtpblib用于电子邮件客户端,并将Flask用作http服务器[gist]:
app.py
from flask import Flask, render_template
from smtp_client import send_email
from smtp_server import SMTPServer
app = Flask(__name__)
@app.route('/send_email')
def email():
server = SMTPServer()
server.start()
try:
send_email()
finally:
server.stop()
return 'OK'
@app.route('/')
def index():
return 'Woohoo'
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
smtp_server.py
# smtp_server.py
import smtpd
import asyncore
import threading
class CustomSMTPServer(smtpd.SMTPServer):
def process_message(self, peer, mailfrom, rcpttos, data):
print('Receiving message from:', peer)
print('Message addressed from:', mailfrom)
print('Message addressed to:', rcpttos)
print('Message length:', len(data))
return
class SMTPServer():
def __init__(self):
self.port = 1025
def start(self):
'''Start listening on self.port'''
# create an instance of the SMTP server, derived from asyncore.dispatcher
self.smtp = CustomSMTPServer(('0.0.0.0', self.port), None)
# start the asyncore loop, listening for SMTP connection, within a thread
# timeout parameter is important, otherwise code will block 30 seconds
# after the smtp channel has been closed
kwargs = {'timeout':1, 'use_poll': True}
self.thread = threading.Thread(target=asyncore.loop, kwargs=kwargs)
self.thread.start()
def stop(self):
'''Stop listening to self.port'''
# close the SMTPserver to ensure no channels connect to asyncore
self.smtp.close()
# now it is safe to wait for asyncore.loop() to exit
self.thread.join()
# check for emails in a non-blocking way
def get(self):
'''Return all emails received so far'''
return self.smtp.emails
if __name__ == '__main__':
server = CustomSMTPServer(('0.0.0.0', 1025), None)
asyncore.loop()
smtp_client.py
import smtplib
import email.utils
from email.mime.text import MIMEText
def send_email():
sender='author@example.com'
recipient='6142546977@tmomail.net'
msg = MIMEText('This is the body of the message.')
msg['To'] = email.utils.formataddr(('Recipient', recipient))
msg['From'] = email.utils.formataddr(('Author', 'author@example.com'))
msg['Subject'] = 'Simple test message'
client = smtplib.SMTP('127.0.0.1', 1025)
client.set_debuglevel(True) # show communication with the server
try:
client.sendmail('author@example.com', [recipient], msg.as_string())
finally:
client.quit()
然后使用python app.py
启动服务器,并在另一个请求中使用/send_email
模拟对curl localhost:5000/send_email
的请求。请注意,要实际发送电子邮件(或短信),您需要跳过以下详细介绍的其他操作:https://blog.codinghorror.com/so-youd-like-to-send-some-email-through-code/。