如何在Python中模拟IMAP服务器,尽管非常懒惰?

13 投票
4 回答
5111 浏览
提问于 2025-04-11 20:38

我很好奇有没有简单的方法可以在Python中模拟一个IMAP服务器(就像imaplib模块那样),而且不想花太多力气。

有没有现成的解决方案?理想情况下,我希望能连接到现有的IMAP服务器,做个数据转储,然后让模拟服务器基于真实的邮箱/邮件结构运行。

说说我懒惰的背景:我有种不好的预感,我正在写的这个小脚本可能会随着时间变得越来越复杂,所以我想创建一个合适的测试环境,但考虑到它可能不会发展,我不想花太多时间去让模拟服务器运行起来。

4 个回答

7

你在做一个测试的时候,真的需要多少东西呢?如果你开始构建一个复杂得像真正服务器的东西,以便在所有测试中使用,那你就走错方向了。只需要模拟出每个测试所需的部分就可以了。

别太费劲去分享一个模拟的实现。它们并不是应该被当作宝贵资源,而是可以随意丢弃的小零件。

8

因为我在Python 3中找不到合适的东西来满足我的需求(Twisted的邮件部分在Python 3中无法运行),所以我用asyncio做了一个小的模拟,你可以根据需要进行改进:

我定义了一个ImapProtocol,它是asyncio.Protocol的扩展。然后像这样启动一个服务器:

factory = loop.create_server(lambda: ImapProtocol(mailbox_map), 'localhost', 1143)
server = loop.run_until_complete(factory)

mailbox_map是一个映射,结构是:邮箱 -> 邮箱列表 -> 消息集合。所以所有的消息和邮箱都保存在内存中。

每当一个客户端连接时,就会创建一个新的ImapProtocol实例。然后,ImapProtocol会为每个客户端执行并回应,支持能力、登录、获取、选择、搜索和存储等功能:

class ImapHandler(object):
    def __init__(self, mailbox_map):
        self.mailbox_map = mailbox_map
        self.user_login = None
        # ...

    def connection_made(self, transport):
        self.transport = transport
        transport.write('* OK IMAP4rev1 MockIMAP Server ready\r\n'.encode())

    def data_received(self, data):
        command_array = data.decode().rstrip().split()
        tag = command_array[0]
        self.by_uid = False
        self.exec_command(tag, command_array[1:])

    def connection_lost(self, error):
        if error:
            log.error(error)
        else:
            log.debug('closing')
            self.transport.close()
        super().connection_lost(error)

    def exec_command(self, tag, command_array):
        command = command_array[0].lower()
        if not hasattr(self, command):
            return self.error(tag, 'Command "%s" not implemented' % command)
        getattr(self, command)(tag, *command_array[1:])

    def capability(self, tag, *args):
        # code for it...
    def login(self, tag, *args):
        # code for it...

在我的测试中,我在设置阶段启动服务器,使用:

self.loop = asyncio.get_event_loop()
self.server = self.loop.run_until_complete(self.loop.create_server(create_imap_protocol, 'localhost', 12345))

当我想模拟一条新消息时:

imap_receive(Mail(to='dest@fakemail.org', mail_from='exp@pouet.com', subject='hello'))

在拆卸阶段停止服务器:

self.server.close()
asyncio.wait_for(self.server.wait_closed(), 1)

详细信息请查看 https://github.com/bamthomas/aioimaplib/blob/master/aioimaplib/tests/imapserver.py


编辑: 我之前的服务器停止有点问题,我用asyncio.Protocol重写了它,并修改了答案以反映这些变化。

10

我上次尝试写IMAP服务器的时候,发现用Twisted框架非常简单。这个框架自带了支持写IMAP服务器的功能,而且你可以有很多灵活的选择。

撰写回答