简单例子:异步发送邮件

我要举的第一个示例是应用程序非常普通的需求:能够发送邮件但是不阻塞主应用。

在这个例子中我会用到 Flask-Mail 扩展,我会假设你们熟悉这个扩展。

我用来说明的示例应用是一个只有一个输入文本框的简单表单。要求用户在此文本框中输入一个电子邮件地址,并在提交,服务器会发送一个测试电子邮件到这个邮件地址。表单中包含两个提交按钮,一个立即发送邮件,一个是一分钟后发送邮件。表单的截图在文章开始。

这里就是支持这个示例的 HTML 模板:

  1. <html>
  2. <head>
  3. <title>Flask + Celery Examples</title>
  4. </head>
  5. <body>
  6. <h1>Flask + Celery Examples</h1>
  7. <h2>Example 1: Send Asynchronous Email</h2>
  8. {% for message in get_flashed_messages() %}
  9. <p style="color: red;">{{ message }}</p>
  10. {% endfor %}
  11. <form method="POST">
  12. <p>Send test email to: <input type="text" name="email" value="{{ email }}"></p>
  13. <input type="submit" name="submit" value="Send">
  14. <input type="submit" name="submit" value="Send in 1 minute">
  15. </form>
  16. </body>
  17. </html>

这里没有什么特别的东西。只是一个普通的 HTML 表单,再加上 Flask 闪现消息。

Flask-Mail 扩展需要一些配置,尤其是电子邮件服务器发送邮件的时候会用到一些细节。为了简单我使用我的 Gmail 账号作为邮件服务器:

  1. # Flask-Mail configuration
  2. app.config['MAIL_SERVER'] = 'smtp.googlemail.com'
  3. app.config['MAIL_PORT'] = 587
  4. app.config['MAIL_USE_TLS'] = True
  5. app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
  6. app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
  7. app.config['MAIL_DEFAULT_SENDER'] = '[email protected]'

注意为了避免我的账号丢失的风险,我将其设置在系统的环境变量,这是我从应用中导入的。

有一个单一的路由来支持这个示例:

  1. @app.route('/', methods=['GET', 'POST'])
  2. def index():
  3. if request.method == 'GET':
  4. return render_template('index.html', email=session.get('email', ''))
  5. email = request.form['email']
  6. session['email'] = email
  7. # send the email
  8. msg = Message('Hello from Flask',
  9. recipients=[request.form['email']])
  10. msg.body = 'This is a test email sent from a background Celery task.'
  11. if request.form['submit'] == 'Send':
  12. # send right away
  13. send_async_email.delay(msg)
  14. flash('Sending email to {0}'.format(email))
  15. else:
  16. # send in one minute
  17. send_async_email.apply_async(args=[msg], countdown=60)
  18. flash('An email will be sent to {0} in one minute'.format(email))
  19. return redirect(url_for('index'))

再次说明,这是一个很标准的 Flask 应用。由于这是一个非常简单的表单,我决定在没有扩展的帮助下处理它,因此我用 request.method 和 request.form 来完成所有的管理。我保存用户在文本框中输入的值在 session 中,这样在页面重新加载后就能记住它。

在这个函数中让人有兴趣的是发送邮件的时候是通过调用一个叫做 send_async_email 的 Celery 任务,该任务调用 delay() 或者 apply_async() 方法。

这个应用的最后一部分就是能够完成作业的异步任务:

  1. @celery.task
  2. def send_async_email(msg):
  3. """Background task to send an email with Flask-Mail."""
  4. with app.app_context():
  5. mail.send(msg)

这个任务使用 celery.task 装饰使得成为一个后台作业。这个函数唯一值得注意的就是 Flask-Mail 需要在应用的上下文中运行,因此需要在调用 send() 之前创建一个应用上下文。

重点注意在这个示例中从异步调用返回值并不保留,因此应用不能知道调用成功或者失败。当你运行这个示例的时候,需要检查 Celery worker 的输出来排查发送邮件的问题。