如何在Python中制作Singleton SMTP客户端安全性?

发布于 2025-02-13 13:53:19 字数 2443 浏览 0 评论 0原文

我不确定我什至不问一个正确的问题,所以请忍受我。

我有一个运行Python代码的Azure函数应用程序。它收到JSON有效载荷,进行一些转换并发送电子邮件。

一切都很好,直到功能开始收到请求爆发。这引起了例外: SMTPLIB.SMTPDATAERROR:(432,B'4.3.2并发连接限制

。 Singleton for Connection Client可以处理此操作:

class EmailSingleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls.__login(*args, **kwargs)
        else:
            try:
                status = cls._instances[cls].noop()[0]
            except smtplib.SMTPServerDisconnected:
                status = -1
            if status != 250:
                log.info("SMTP Client disconnected")
                cls.__login(*args, **kwargs)
        return cls._instances[cls]

    def __login(cls, host, port, user, password, timeout):
        log.info("Refreshing SMTP client")
        cls._instances[cls] = super(EmailSingleton, cls).__call__(host=host, port=port, timeout=timeout)
        cls._instances[cls].starttls()
        cls._instances[cls].login(user=user, password=password)

class EmailConnectionClientSingleton(smtplib.SMTP, metaclass=EmailSingleton):
    pass

仅通过打电话来使用它:

EmailConnectionClientSingleton(
    host=host,
    port=port,
    timeout=timeout,
    user=user,
    password=password
).send_message(msg)

当请求一个一个时,这可以正常工作,但是我得到了各种有趣的异常在爆发中到达时:

  • smtplib.smtpdataerror :( 503,b'5.5.1命令的不良顺序')
  • smtplib.smtpdataeror:(250,b'2.1。 5收件人OK')
  • smtplib.smtpdataerror:(250,b'2.0.0 ok')
  • smtplib.smtprecipientrefused:{' [email  procepted] ':(503,

b'5.5.5.5.5.5.5.5.5.5.1响应不秩序。只有大约1/3的电子邮件成功。

我应该如何解决这个问题?我什至应该使用单身客户端(这告诉我我应该: https://learn.microsoft.com/en-us/azure/azure-functions/manage-connections?tabs = csharp#static-clients )?我会尝试以某种方式使我的Singleton客户端线程安全吗?我会为send_message呼叫创建队列吗?我会创建一个从其他功能发送电子邮件的工人吗?

我尝试使用_lock = threading.lock()创建客户端时,但这无济于事,因为问题似乎是在使用客户端而不是创建客户端。

I'm unsure if I'm even asking the correct question, so please bear with me.

I have an Azure Function App that runs python code. It receives a JSON payload, does some transformations, and sends an email.

Everything worked fine until the function started receiving bursts of requests. This caused exceptions:
smtplib.SMTPDataError: (432, b'4.3.2 Concurrent connections limit exceeded. Visit https://aka.ms/concurrent_sending for more information. [Hostname=xxxxxxxx]')

I decided to create a singleton for connection client to work around that:

class EmailSingleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls.__login(*args, **kwargs)
        else:
            try:
                status = cls._instances[cls].noop()[0]
            except smtplib.SMTPServerDisconnected:
                status = -1
            if status != 250:
                log.info("SMTP Client disconnected")
                cls.__login(*args, **kwargs)
        return cls._instances[cls]

    def __login(cls, host, port, user, password, timeout):
        log.info("Refreshing SMTP client")
        cls._instances[cls] = super(EmailSingleton, cls).__call__(host=host, port=port, timeout=timeout)
        cls._instances[cls].starttls()
        cls._instances[cls].login(user=user, password=password)

class EmailConnectionClientSingleton(smtplib.SMTP, metaclass=EmailSingleton):
    pass

and use it simply by calling:

EmailConnectionClientSingleton(
    host=host,
    port=port,
    timeout=timeout,
    user=user,
    password=password
).send_message(msg)

This works fine when requests come one by one, but I get all kinds of fun exceptions when they arrive in bursts:

  • smtplib.SMTPDataError: (503, b'5.5.1 Bad sequence of commands')
  • smtplib.SMTPDataError: (250, b'2.1.5 Recipient OK')
  • smtplib.SMTPDataError: (250, b'2.0.0 OK')
  • smtplib.SMTPRecipientsRefused: {'[email protected]': (503, b'5.5.1 Bad sequence of commands')}

It looks like all of the above are caused by handshakes overlapping and responses being out of order. Only about 1/3rd of emails succeed.

How should I approach this problem? Should I even use a singleton client (This tells me I should: https://learn.microsoft.com/en-us/azure/azure-functions/manage-connections?tabs=csharp#static-clients)? Do I try to make my singleton client thread-safe somehow? Do I create a queue for the send_message calls? Do I create a worker that sends emails from a different function?

I tried using _lock = threading.Lock() when creating the client, but that didn't help since the problem appears to be with using the client and not with creating it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

初心 2025-02-20 13:53:20

我最终使用queue.queue

class Singleton(type):
    _instance = None
    _lock = Lock()

    def __call__(cls, *args, **kwargs):
        if not cls._instance:
            with cls._lock:
                # another thread could have created the instance
                # before we acquired the lock. So check that the
                # instance is still nonexistent.
                if not cls._instance:
                    cls._instance = super().__call__(*args, **kwargs)
        return cls._instance


class QueueSingleton(Queue, metaclass=Singleton):
    pass

而不是立即发送电子邮件,而是将其放在队列中:

queue = QueueSingleton()
queue.put(msg)
log.info("Email put in the queue")

然后,我创建了一个新的Azure函数,该功能使用队列发送电子邮件。

queue = QueueSingleton()

while start_time + FUNCTION_MAX_LIFE > datetime.now():
    try:
        msg = queue.get(timeout=300)
        log.info("Got an email to send!")
    except Empty:
        # If we didn't get anything to send during timeout window
        # end the execution and allow TimeTrigger to spawn a new worker
        log.info("No emails to send. Quitting.")
        break

    try:
        email_connection.send_message(msg)
        queue.task_done()
        log.info(f'Email sent: {msg["To"]}')

感觉很笨拙,但似乎在起作用。

I ended up using queue.Queue:

class Singleton(type):
    _instance = None
    _lock = Lock()

    def __call__(cls, *args, **kwargs):
        if not cls._instance:
            with cls._lock:
                # another thread could have created the instance
                # before we acquired the lock. So check that the
                # instance is still nonexistent.
                if not cls._instance:
                    cls._instance = super().__call__(*args, **kwargs)
        return cls._instance


class QueueSingleton(Queue, metaclass=Singleton):
    pass

Instead of sending the email immediately I put it in the queue:

queue = QueueSingleton()
queue.put(msg)
log.info("Email put in the queue")

Then I created a new Azure Function that uses the queue to send emails.

queue = QueueSingleton()

while start_time + FUNCTION_MAX_LIFE > datetime.now():
    try:
        msg = queue.get(timeout=300)
        log.info("Got an email to send!")
    except Empty:
        # If we didn't get anything to send during timeout window
        # end the execution and allow TimeTrigger to spawn a new worker
        log.info("No emails to send. Quitting.")
        break

    try:
        email_connection.send_message(msg)
        queue.task_done()
        log.info(f'Email sent: {msg["To"]}')

It feels junky but it appears to be working.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文