IMAP更加完善而功能强大。Twisted是一个使用Python编写网络应用程序的框架,它被设计成多任务,并且是贯穿整个库都使用异步I/O的。


一、Twisted中的IMAP简介

大多数网络客户端库,如poplib和imaplib工作的方式都一样。您需要编写调用这些库的程序,库调用服务器和您的代码(也就是说,函数并没有返回),直到从网络上得到一个结果。

Twisted在它的头部返回这种方法。当您访问网络的时候,您通知Twisted该把结果传递给哪个函数,您真正的调用会立刻返回,当在网络上取得一个结果的时候,您的函数将被调用,而给结果将作为一个参数。Twisted开发人员把这种情况称为“别调用我们,我们将调用你”,这个模式也适用于基于事件的程序。被一个结果调用的函数也称为回调函数。


二、理解Twisted基础

#!/usr/bin/env python

from twisted.internet import defer, reactor, protocol
from twisted.mail.imap4 import IMAP4Client
import sys

class IMAPClient(IMAP4Client):
    def connectionMade(self):
        print 'I have successfully connected to the server'
        d=self.getCapabilities()
        d.addCallback(self.gotcapabilities)
        
    def gotcapabilities(self, caps):
        if caps==None:
            print 'Server did not return a capability list.'
        else:
            for key, value in caps.items():
                print '%s: %s' %(key, str(value))
                
        self.logout()
        reactor.stop()
        
class IMAPFactory(protocol.ClientFactory):
    protocol=IMAPClient
    
    def clientConnectionFailed(self, connector, reason):
        print 'Client connection failed:', reason
        reactor.stop()
        
reactor.connectTCP(sys.argv[1], 143, IMAPFactory())
reactor.run()

大多数Twisted客户端程序基本上都包含两个类:一个protocol类(例子中的IMAPClient)和一个factory类(IMAPFactory)。factory类管理和服务器的链接,protocol类实现和服务器的会话。例子中,当程序开始的时候,先建立一个网络链接,默认IMAP端口143,同时一个IMAPFactory对象也被传递过来。程序最后一行reactor.run()。在Twisted中,reactor是用来处理网络事件的部分,直到reactor.stop()被调用,对reactor.run()的调用才返回。在建立链接后,reactor调用connectionMade()。

getCapabilities()可以返回IMAP服务器支持的一系列可选的特性,它返回一个被称为Deferred的对象,它是Twisted中基于事件的模型核心。它用来通知Twisted在收到一个回复的时候该做什么。一旦收到一个Deferred对象,程序就调用该对象上的addCallback,这个函数通知reactor在收到应答后该调用什么函数。在这里,gotcapabilities是唯一没有重载基类方法中的方法。在调用gotcapabilities时没有使用括号是因为您不想在调用addCallback()的时候调用gotcapabilities(),相反,您只是把函数传递给addCallback(),并让Twisted稍后调用它。gotcapabilities函数从Twisted收到一个参数,这个参数是您向getCapabilities()提交请求的结果。最后登出并停止reactor,程序结束。


1、登录:

#!/usr/bin/env python
#-*-coding:utf-8-*-

from twisted.internet import defer, reactor, protocol
from twisted.mail.imap4 import IMAP4Client
import sys, getpass

class IMAPClient(IMAP4Client):
    def connectionMade(self):
        print 'I have successfully connected to the server'
        IMAPLogin(self)
        print 'connectionMade returning'
        
        
class IMAPFactory(protocol.ClientFactory):
    protocol=IMAPClient
    
    def __init__(self, username, password):
        self.username=username
        self.password=password


    def clientConnectionFailed(self, connector, reason):
        print 'Client connection failed:', reason
        reactor.stop()
        
class IMAPLogin(object):
    def __init__(self, proto):
        self.proto=proto
        self.factory=proto.factory
        
        d=self.proto.login(self.factory.username, self.factory.password)
        
        d.addCallback(self.loggedin)
        d.addCallback(self.stopreactor)
        
        print "IMAPLogic.__init__returning"
        
    def loggedin(self, data):
        print "I'm logged in"
        return self.logout()
        
    def logout(self):
        print 'Logging out'
        d=self.proto.logout()
        return d
        
    def stopreactor(self, data=None):
        print 'Stopping reactor'
        reactor.stop()
        
password=getpass.getpass("Enter password for %s on %s: " %(sys.argv[2], sys.argv[1]))

        
reactor.connectTCP(sys.argv[1], 143, IMAPFactory(sys.argv[2], password))
reactor.run()

程序首先进行reactor的连接,然后reactor运行run。这会调用protol类中的connectionMade方法。connecitonMade初始化一个IMAPLogic的实例。初始化IMAPLogic实例时,protol类调用了login方法生成一个deferred对象d,然后d增加两个callback函数。deferred(递延)对象,当从网络中获得结果后,便把结果作为参数并调用callback函数。在这个例子中,当用户登录时,Twisted从网络中获取到应答,然后把它作为参数调用loggedin函数。loggedin函数掉哟功能logout这一函数而logout函数返回proto类的logout方法生成的deferred对象。因为这一对象没有callback函数,所以被直接传递给第一个deferred对象的下一个callback函数,即stopreactor()。

可以简单地认为这几个函数之间传递的是deferred对象。。。(个人理解)


2、错误处理

Twisted无法产生异常,但有一个调用errback的概念,和callback类似,但它只在有错误发生的时候才被调用。

#!/usr/bin/env python

from twisted.internet import defer, reactor, protocol
from twisted.mail.imap4 import IMAP4Client
import sys, getpass

class IMAPClient(IMAP4Client):
    def connectionMade(self):
        print 'I have successfully connected to the server'
        IMAPLogic(self)
        print 'connectionMade returning'
        
class IMAPFactory(protocol.ClientFactory):
    protocol=IMAPClient
    
    def __init__(self, username, password):
        self.username=username
        self.password=password
        
    def clientConnectionFailed(self, connector, reason):
        print 'Client connection failed:', reason
        reactor.stop()
        
class IMAPLogic(object):
    def __init__(self, proto):
        self.proto=proto
        self.factory=proto.factory
        self.logintries=1
        
        d=self.login()
        d.addCallback(self.loggedin)
        d.addErrback(self.loginerror)
        d.addCallback(self.logout)
        d.addCallback(self.stopreactor)
        
        d.addErrback(self.errorhappened)
        
        print 'IMAPLogic.__init__ returning'
        
    def login(self):
        print 'Logging in..'
        return self.proto.login(self.factory.username, self.factory.password)
        
    def loggedin(self, data):
        print "I'm logged in"
        
    def logout(self, data=None):
        d=self.proto.logout()
        return d
        
    def stopreactor(self, data=None):
        print 'Stopping reactor'
        reactor.stop()
        
    def errorhappened(self, failure):
        print 'An error occured:', failure.getErrorMessage()
        print 'Because of the error , I am logging out and stopping reactor...'
        d=self.logout()
        d.addBoth(self.stopreactor)
        return failure
    
    def loginerror(self, failure):
        print 'Your login failed (attempt %d).' % self.logintries
        if self.logintries>=3:
            print 'You have triedto log in three times;I am giving up'
            return failure
        self.logintries+=1
        
        sys.stdout.write('New username: ')
        self.factory.username=sys.stdin().readline().strip().encode('utf-8')
        self.factory.password=getpass.getpass("New password: ").encode('utf-8')
        
        d=self.login()
        d.addErrback(self.loginerror)
        return d
        
password=getpass.getpass("Enter password for %s on %s: " % (sys.argv[2], sys.argv[1])).encode('utf-8')
reactor.connectTCP(sys.argv[1], 143, IMAPFactory(sys.argv[2].encode('utf-8'), password))
reactor.run()


3、基本下载

下载整个邮箱:

#!/usr/bin/env python

from twisted.internet import defer,reactor,protocol
from twisted.mail.imap4 import IMAP4Client
import sys, getpass, email

class IMAPClient(IMAP4Client):
    def connectionMade(self):
        IMAPLogin(self)

class IMAPFactory(protocol.ClientFactory):
    protocol=IMAPClient
    def __init__(self, username, password):
        self.username=username
        self.password=password
    
    def clientConnectionFailed(self, connector, reason):
        print 'Client connnection failed:', reason
        reactor.stop()
        
class IMAPLogin(object):
    def __init__(self, proto):
        self.proto=proto
        self.factory=proto.factory
        d=self.proto.login(self.factory.username, self.factory.password)
        d.addCallback(lambda x: self.proto.examine('INBOX'))
        d.addCallback(lambda x: self.proto.fetchSpecific('1:*', peek=1))
        d.addCallback(self.gotmessages)
        d.addCallback(self.logout)
        d.addCallback(self.stopreactor)
        
        d.addErrback(self.errorhappened)
        
    def gotmessages(self, data):
        destfd=open(sys.argv[3], 'at')
        for key, value in data.items():
            print 'Writing message', key
            msg=email.message_from_string(value[0][2])
            destfd.write(msg.as_string(unixfrom=1))
            destfd.write('\n')
        destfd.close()

    def logout(self, data=None):
        return self.proto.logout()
        
    def stopreactor(self, data=None):
        reactor.stop()
        
    def errorhappened(self, failure):
        print 'An error occured:', failure.getErrorMessage()
        print 'Because of the error, I am logging out and stopping reactor...'
        d=self.logout()
        d.addBoth(self.stopreactor)
        return failure
        
password=getpass.getpass('Enter password for %s on %s:'%(sys.argv[2], sys.argv[1]))
#print password
reactor.connectTCP(sys.argv[1], 143, IMAPFactory(sys.argv[2], password))
reactor.run()

fetchSpecific()函数可以从一封邮件文件夹中下载一封多封邮件。第一个参数,'1:*'是该文件夹中对应的所有邮件范围,通过把peek设置成true,邮件在您阅读他们的时候并不被更改,如果peek没有被设置,所有被下载的邮件都会添加上\Seen标志。gotmessages()函数被下载的邮件调用,它传入一个字典,该字典的键就是邮件的number,它的值是该邮件组成成分的列表。