Flask -Очередь сообщений электронной почты для отправки на разные электронные письма - PullRequest
5 голосов
/ 03 августа 2020

Я использую библиотеку Flask -Mail для моего приложения Flask, чтобы отправлять пользователю приветственное письмо по умолчанию, когда он подписывается на добавление в информационный бюллетень. После отладки библиотеки я обнаружил, что она может обрабатывать только одно соединение за раз для отправки сообщения, а затем автоматически закрывает соединение. Если серверная часть отправляет электронное письмо другому пользователю, пока соединение все еще открыто, он генерирует это исключение: raise SMTPServerDisconnected("Connection unexpectedly closed: " smtplib.SMTPServerDisconnected: Connection unexpectedly closed: [WinError 10054] An existing connection was forcibly closed by the remote host. Я хочу иметь возможность поставить в очередь почтовую библиотеку Mail для отправки нового сообщения другому получателю после закрытия соединения, но в настоящее время он продолжает выдавать ошибку, о которой я упоминал выше, когда я пытаюсь поставить функцию в очередь для отправки сообщения.

worker.py:

import os
import redis
from rq import Worker, Queue, Connection

listen = ['high', 'default', 'low']

redis_url = os.environ.get('REDISTOGO_URL')

conn = redis.from_url(redis_url)

if __name__ == '__main__':
    with Connection(conn):
        worker = Worker(map(Queue, listen))
        worker.work()

user.routes.py

from flask import request, Blueprint, redirect, render_template
from flask_app import mail, db
from flask_app.users.forms import NewsLetterRegistrationForm
from flask_app.models import User
from flask_mail import Message
from rq import Queue
from worker import conn
import os, time

users = Blueprint("users", __name__)
queue = Queue(connection=conn)

@users.route("/newsletter-subscribe", methods=["GET", "POST"])
def newsletter_subscribe():
    form = NewsLetterRegistrationForm()
    if form.validate_on_submit():
        user = User(name=form.name.data, email=form.email.data)
        db.session.add(user)
        db.session.commit()
        queue.enqueue(send_welcome_email(user))

        return "Success"
    return "Failure"

def send_welcome_email(user):
    with mail.connect() as con:
        html = render_template("welcome-email.html", name=user.name)
        subject = "Welcome!"
        msg = Message(
            subject=subject,
            recipients=[user.email],
            html=html
        )
        con.send(msg)

main.routes.py

from flask import render_template, session, request, current_app, Blueprint, redirect, url_for, json, make_response
from flask_app.users.forms import NewsLetterRegistrationForm
import os

main = Blueprint("main", __name__)

@main.route("/", methods=["GET"])
def index():
    return render_template("index.html", title="Home")

@main.route("/example", methods=["GET"])
def example():
    return render_template("example.html", title="example")

@main.context_processor
def inject_template_scope():
    injections = dict()
    form = NewsLetterRegistrationForm()
    injections.update(form=form)
    return injections

_ init _. py

from logging.config import dictConfig
from flask import Flask, url_for, current_app
from flask_bcrypt import Bcrypt
from flask_sqlalchemy import SQLAlchemy
from flask_talisman import Talisman
from flask_compress import Compress
from flask_mail import Mail
import os

config = {
    "SECRET_KEY": os.environ.get("SECRET_KEY"),
    "DEBUG": True,
    "SQLALCHEMY_DATABASE_URI": os.environ.get("DATABASE_URL"),
    "SQLALCHEMY_TRACK_MODIFICATIONS": False,
    "SQLALCHEMY_ECHO": False,
    "MAIL_SERVER": "mail.privateemail.com",
    "MAIL_PORT": 587,
    "MAIL_USE_SSL": False,
    "MAIL_USE_TLS": True,
    "MAIL_USERNAME": "test@example.com",
    "MAIL_PASSWORD": os.environ.get("NEWS_MAIL_PASSWORD"),
    "MAIL_DEFAULT_SENDER": "test@example.com"
}

talisman = Talisman()
db = SQLAlchemy()
bcrypt = Bcrypt()
compress = Compress()
mail = Mail()
app = Flask(__name__)

def create_app():
    app.config.from_mapping(config)
    talisman.init_app(app)
    db.init_app(app)
    bcrypt.init_app(app)
    compress.init_app(app)
    mail.init_app(app)

    from flask_app.users.routes import users
    app.register_blueprint(users)

    with app.app_context():
        db.create_all()
    return app

run.py

from flask_app import create_app

Журнал ошибок:

Traceback (most recent call last):
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\smtplib.py", line 391, in getreply
    line = self.file.readline(_MAXLINE + 1)
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\socket.py", line 669, in readinto
    return self._sock.recv_into(b)
ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 2464, in __call__
    return self.wsgi_app(environ, start_response)
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 2450, in wsgi_app
    response = self.handle_exception(e)
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 1867, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\_compat.py", line 39, in reraise
    raise value
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\_compat.py", line 39, in reraise
    raise value
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\User\Work Stuff\example.com\flask_app\users\routes.py", line 18, in newsletter_subscribe
    send_welcome_email(user, request.host_url)
  File "C:\User\Work Stuff\example.com\flask_app\users\routes.py", line 42, in send_welcome_email 
    with mail.connect() as con:
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask_mail.py", 
line 144, in __enter__
    self.host = self.configure_host()
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask_mail.py", 
line 158, in configure_host
    host = smtplib.SMTP(self.mail.server, self.mail.port)
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\smtplib.py", line 253, in __init__
    (code, msg) = self.connect(host, port)
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\smtplib.py", line 341, in connect
    (code, msg) = self.getreply()
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\smtplib.py", line 394, in getreply
    raise SMTPServerDisconnected("Connection unexpectedly closed: "
smtplib.SMTPServerDisconnected: Connection unexpectedly closed: [WinError 10054] An existing connection was forcibly closed by the remote host

1 Ответ

1 голос
/ 10 августа 2020

Мне кажется, что ваш почтовый сервер закрывает соединение, потому что вы делаете больше запросов, чем позволяет его конфигурация. Если вы используете стороннего почтового провайдера, вы можете проверить, предлагает ли используемый вами сервис какой-либо способ отправки массовых писем, например, через API или загрузку файлов. Или если у них есть способ изменить эту конфигурацию для вас.

Если это невозможно:

Одним из решений может быть вызов блокировки (time.sleep() ), чтобы снизить частоту отправки писем:

import time

def send_welcome_email(user):
    with mail.connect() as con:
        html = render_template("welcome-email.html", name=user.name)
        subject = "Welcome!"
        msg = Message(
            subject=subject,
            recipients=[user.email],
            html=html
        )
        time.sleep(2) # 2 seconds, modify as you see fit
        con.send(msg)

Другим решением было бы заключить код в блок try catch, а при исключениях немного подождать, прежде чем пытаться отправить напишите еще раз.

def send_welcome_email(user, is_retry=False):
    try:
        with mail.connect() as con:
            html = render_template("welcome-email.html", name=user.name)
            subject = "Welcome!"
            msg = Message(
                subject=subject,
                recipients=[user.email],
                html=html
            )
            con.send(msg)
    except SMTPServerDisconnected:
        time.sleep(60) # Wait for a while, 1 minute tends to be a good measure as most configurations specify how many requests can be made a minute. 
        if not is_retry:  # Only retry once -> you could modify this to make the use of a counter.
            send_welcome_email(user, is_retry=True) # Try again
   
...