【问题标题】:Twisted deferreds firing in undesired way扭曲的延迟以不希望的方式触发
【发布时间】:2013-01-28 18:10:36
【问题描述】:

我有以下代码

# logging
from twisted.python import log

import sys

# MIME Multipart handling
import email
import email.mime.application
import uuid

# IMAP Connection
from twisted.mail import imap4
from twisted.internet import protocol


#SMTP Sending
import os.path
from OpenSSL.SSL import SSLv3_METHOD
from twisted.internet import ssl
from twisted.mail.smtp import ESMTPSenderFactory
from twisted.internet.ssl import ClientContextFactory
from twisted.internet.defer import Deferred
from twisted.internet import reactor

#class AccountsManager(object):


def connectToIMAPServer(imap_server, username, password):
    factory = IMAP4ClientFactory(username, password, login_insecure = True)

    host, port = imap_server.split(":")

    # connect to reactor
    if port == '993':
        reactor.connectSSL(host, int(port), factory, ssl.ClientContextFactory())
    else:
        if not port:
            port = 143
        reactor.connectTCP(host, int(port), factory)

    d = factory.deferred
    d.addCallback(lambda r: factory.proto)

    return d

class IMAP4Client(imap4.IMAP4Client):
    """
        A client with callbacks for greeting messages from an IMAP server.
        """
    greetDeferred = None

    def serverGreeting(self, caps):
        self.serverCapabilities = caps
        if self.greetDeferred is not None:
            d, self.greetDeferred = self.greetDeferred, None
            d.callback(self)

class IMAP4ClientFactory(protocol.ClientFactory):
    usedUp = False

    protocol = IMAP4Client


    def __init__(self, username, password, mailbox = "INBOX", login_insecure = False):
        self.ctx = ssl.ClientContextFactory()

        self.username = username
        self.password = password
        self.mailbox  = mailbox
        self.login_insecure = login_insecure

        self.deferred = Deferred()

    def buildProtocol(self, addr):
        """
            Initiate the protocol instance. Since we are building a simple IMAP
            client, we don't bother checking what capabilities the server has. We
            just add all the authenticators twisted.mail has.  Note: Gmail no
            longer uses any of the methods below, it's been using XOAUTH since
            2010.
            """
        assert not self.usedUp
        self.usedUp = True

        p = self.protocol(self.ctx)
        p.factory = self

        p.greetDeferred = self.deferred

        p.registerAuthenticator(imap4.PLAINAuthenticator(self.username))
        p.registerAuthenticator(imap4.LOGINAuthenticator(self.username))
        p.registerAuthenticator(imap4.CramMD5ClientAuthenticator(self.username))

        self.deferred.addCallback(self.GreetingCallback)
        self.deferred.addErrback(self.GreetingErrback)

        self.proto = p

        return p

    def GreetingCallback(self, result):
        print "Secure Login"
        auth_d = self.proto.authenticate(self.password)
        auth_d.addCallback(self.AuthenticationCallback)
        auth_d.addErrback(self.AuthenticationErrback)

        return auth_d # attach it to the main deferred

    def GreetingErrback(self, error):
        log.err(error)
        self.CloseConnection()
        return error

    def AuthenticationCallback(self, result):
        print "Selecting Mailbox"
        d = self.proto.examine(self.mailbox)
        return d

    def AuthenticationErrback(self, failure):
        if self.login_insecure:
            failure.trap(imap4.NoSupportedAuthentication)
            return self.InsecureLogin()
        else:
            return error

    def InsecureLogin(self):
        print "Insecure Login"
        d = self.proto.login(self.username, self.password)
        d.addCallback(self.AuthenticationCallback)
        return d

    def CloseConnection(self):
        self.proto.transport.loseConnection()

    def clientConnectionFailed(self, connector, reason):
        d, self.deferred = self.deferred, None
        d.errback(reason)


class MailServer(object):
    "Manages a server"

    size = 0
    used_space = 0


    def __init__(self, smtp_server, imap_server, username, password):
        self.smtp_server, self.smtp_port = smtp_server.split(":")
        self.imap_server, self.imap_port = imap_server.split(":")
        self.username = username
        self.password = password

        self.imap_connection = IMAP4ClientFactory(username, password)


    def upload_data(self, data):
        """
        Uploads data to email server returns deferred that will return with the imap uid
        """

        # Create a text/plain message
        id = str(uuid.uuid4()).upper()

        msg = email.mime.Multipart.MIMEMultipart()
        msg['Subject'] = 'GMA ID: %s' % id
        msg['From'] = self.email_address
        msg['To'] = self.email_address

        # The main body is just another attachment
        body = email.mime.Text.MIMEText("GMA ID: %s" % (self.uuid_id))
        msg.attach(body)
        att = email.mime.application.MIMEApplication(data,_subtype="raw")
        att.add_header('Content-Disposition','attachment',filename = os.path.basename(self.filename))
        msg.attach(att)

        # Create a context factory which only allows SSLv3 and does not verify
        # the peer's certificate.
        contextFactory = ClientContextFactory()
        contextFactory.method = SSLv3_METHOD

        d = Deferred()

        mime_obj = StringIO(str(msg))

        senderFactory = ESMTPSenderFactory(
                                           self.username,
                                           self.password,
                                           self.email_address,
                                           self.email_address,
                                           mime_obj,
                                           d,
                                           contextFactory=contextFactory)

        d.addCallback(lambda r: self.email_sent(id, int(self.parts)) )
        d.addErrback(self.email_error)

        reactor.connectTCP(self.smtp_server, self.smtp_port, senderFactory)

        d.addCallback(self.upload_success, *args, **kw)
        d.addErrback(self.upload_error, 1)

        return d


    def upload_success(self, result):
        print "upload was succesful!"

    def upload_error(self, result):
        print "upload error"

    def download_data(self, uid):
        """
        Downloads data from the email server returns a deferred that will return with the data
        """
        print "uid"


if __name__ == "__main__":
    log.startLogging(sys.stdout)
    d = connectToIMAPServer("imap.gmail.com:993", "username", "password")
    def f(s):
        print s


    d.addCallback(lambda r: f("These are fired before the auth and examine callbacks, why?"))
    d.addCallback(lambda r: f("These are fired before the auth and examine callbacks, why?"))
    reactor.run()

该类假设处理登录和选择邮箱并很好地返回一个准备使用的 IMAP 原型,但是底部的两个回调在其他回调之前被触发,我明白为什么,回调是在其他回调之前添加的因为尚未调用 buildProtocol,所以最好的处理方法是什么,只需在 init 中添加一个虚拟回调来“保持”第一个位置?

【问题讨论】:

    标签: python asynchronous twisted


    【解决方案1】:
    from twisted.internet.endpoints import TCP4ClientEndpoint
    d = TCP4ClientEndpoint(reactor, host, int(port)).connect(factory)
    

    d.addCallback(lambda r: factory.deferred)    
    

    而不是

    d = factory.deferred
    

    connectToIMAPServer 中应该这样做 - 您的factory.deferred 只有在协议准备好后才会返回。 (Twisted Documentation on writing clients)

    【讨论】:

    【解决方案2】:

    我最终编辑了代码,只是在内部管理了 deferred 的回调或 errback 更新代码

    # logging
    from twisted.python import log
    
    
    import sys
    
    # MIME Multipart handling
    import email
    import email.mime.application
    import uuid
    
    # IMAP Connection
    from twisted.mail import imap4
    from twisted.internet import protocol
    
    
    #SMTP Sending
    import os.path
    from OpenSSL.SSL import SSLv3_METHOD
    from twisted.internet import ssl
    from twisted.mail.smtp import ESMTPSenderFactory
    from twisted.internet.ssl import ClientContextFactory
    from twisted.internet.defer import Deferred
    from twisted.internet import reactor
    
    #class AccountsManager(object):
    
    
    def connectToIMAPServer(imap_server, username, password):
        factory = IMAP4ClientFactory(username, password, login_insecure = True)
    
        host, port = imap_server.split(":")
    
        # connect to reactor
        if port == '993':
            reactor.connectSSL(host, int(port), factory, ssl.ClientContextFactory())
        else:
            if not port:
                port = 143
            reactor.connectTCP(host, int(port), factory)
    
        return factory.deferred
    
    class IMAP4Client(imap4.IMAP4Client):
        """
            A client with callbacks for greeting messages from an IMAP server.
            """
        greetDeferred = None
    
        def serverGreeting(self, caps):
            self.serverCapabilities = caps
            if self.greetDeferred is not None:
                d, self.greetDeferred = self.greetDeferred, None
                d.callback(self)
    
    class IMAP4ClientFactory(protocol.ClientFactory):
        usedUp = False
    
        protocol = IMAP4Client
    
    
        def __init__(self, username, password, mailbox = "INBOX", login_insecure = False):
            self.ctx = ssl.ClientContextFactory()
    
            self.username = username
            self.password = password
            self.mailbox  = mailbox
            self.login_insecure = login_insecure
    
            # called when the protocol is all set up or there is an error setting it up
            self.deferred = Deferred()
    
        def buildProtocol(self, addr):
            """
                Initiate the protocol instance. Since we are building a simple IMAP
                client, we don't bother checking what capabilities the server has. We
                just add all the authenticators twisted.mail has.  Note: Gmail no
                longer uses any of the methods below, it's been using XOAUTH since
                2010.
                """
            assert not self.usedUp
            self.usedUp = True
    
            p = self.protocol(self.ctx)
            p.factory = self
    
            # deferred for when the IMAP Greeting is done
            p.greetDeferred = Deferred()
    
            p.registerAuthenticator(imap4.PLAINAuthenticator(self.username))
            p.registerAuthenticator(imap4.LOGINAuthenticator(self.username))
            p.registerAuthenticator(imap4.CramMD5ClientAuthenticator(self.username))
    
            p.greetDeferred.addCallback(self.GreetingCallback)
            p.greetDeferred.addErrback(self.GreetingErrback)
    
            self.proto = p
    
            return p
    
        def GreetingCallback(self, result):
            log.msg("Succesfully sent IMAP Greeting.")
            auth_d = self.proto.authenticate(self.password)
            auth_d.addCallback(self.AuthenticationCallback)
            auth_d.addErrback(self.AuthenticationErrback)
    
            return auth_d
    
        def GreetingErrback(self, error):
            log.msg("Problem sending greeting")
            log.err(error)
            self.CloseConnection()
            self.deferred.errback(error)
    
        def AuthenticationCallback(self, result):
            log.msg("Authenticated")
            log.msg("Selecting Mailbox")
            d = self.proto.examine(self.mailbox)
            d.addCallback(self.MailboxSelectCallback)
            d.addErrback(self.MailboxSelectErrback)
            return d
    
        def AuthenticationErrback(self, failure):
            log.msg("Unable to authenticate securly")
            if self.login_insecure:
                log.msg("Trying to login insecurly")
                failure.trap(imap4.NoSupportedAuthentication)
                return self.InsecureLogin()
            else:
                log.err(failure)
                self.deferred.errback(failure)
    
        def InsecureLogin(self):
            log.msg("Logging in insecurly")
            d = self.proto.login(self.username, self.password)
            d.addCallback(self.AuthenticationCallback)
            return d
    
        def MailboxSelectCallback(self, result):
            # connected and protocol set up
            log.msg("IMAP4 protocol setup")
            self.deferred.callback(self.proto)
    
    
        def MailboxSelectErrback(self, error):
            log.msg("Cannot select mailbox %s" % self.mailbox)
            log.err(error)
            self.deferred.errback(error)
    
    
        def CloseConnection(self):
            self.proto.transport.loseConnection()
    
        def clientConnectionFailed(self, connector, reason):
            log.msg("Connecting was lost")
            log.err(reason)
            d, self.deferred = self.deferred, None
            d.errback(reason)
    
    
    class MailServer(object):
        "Manages a server"
    
        size = 0
        used_space = 0
    
    
        def __init__(self, smtp_server, imap_server, username, password):
            self.smtp_server, self.smtp_port = smtp_server.split(":")
            self.imap_server, self.imap_port = imap_server.split(":")
            self.username = username
            self.password = password
    
            self.imap_connection = IMAP4ClientFactory(username, password)
    
    
        def upload_data(self, data):
            """
            Uploads data to email server returns deferred that will return with the imap uid
            """
    
            # Create a text/plain message
            id = str(uuid.uuid4()).upper()
    
            msg = email.mime.Multipart.MIMEMultipart()
            msg['Subject'] = 'GMA ID: %s' % id
            msg['From'] = self.email_address
            msg['To'] = self.email_address
    
            # The main body is just another attachment
            body = email.mime.Text.MIMEText("GMA ID: %s" % (self.uuid_id))
            msg.attach(body)
            att = email.mime.application.MIMEApplication(data,_subtype="raw")
            att.add_header('Content-Disposition','attachment',filename = os.path.basename(self.filename))
            msg.attach(att)
    
            # Create a context factory which only allows SSLv3 and does not verify
            # the peer's certificate.
            contextFactory = ClientContextFactory()
            contextFactory.method = SSLv3_METHOD
    
            d = Deferred()
    
            mime_obj = StringIO(str(msg))
    
            senderFactory = ESMTPSenderFactory(
                                               self.username,
                                               self.password,
                                               self.email_address,
                                               self.email_address,
                                               mime_obj,
                                               d,
                                               contextFactory=contextFactory)
    
            d.addCallback(lambda r: self.email_sent(id, int(self.parts)) )
            d.addErrback(self.email_error)
    
            reactor.connectTCP(self.smtp_server, self.smtp_port, senderFactory)
    
            d.addCallback(self.upload_success, *args, **kw)
            d.addErrback(self.upload_error, 1)
    
            return d
    
    
        def upload_success(self, result):
            print "upload was succesful!"
    
        def upload_error(self, result):
            print "upload error"
    
        def download_data(self, uid):
            """
            Downloads data from the email server returns a deferred that will return with the data
            """
            print "uid"
    
    
    if __name__ == "__main__":
        log.startLogging(sys.stdout)
        d = connectToIMAPServer("imap.gmail.com:993", "email", "password")
        def f(s):
            print s
    
    
        d.addCallback(lambda r: f("These are fired before the auth and examine callbacks, why?"))
        d.addCallback(lambda r: f("These are fired before the auth and examine callbacks, why?"))
        reactor.run()
    

    【讨论】:

    • 所以本质上你是从MailboxSelectCallback 调用self.deferred.callback,而不是把它变成greetDeferred。看起来不错,特别是如果您需要确保在触发 deferred 之前所有前面的步骤都成功。 (评论以防有人想知道发生了什么变化)
    • 是的,我查看了 twisted.web.client 工厂,他们似乎就是这样做的,所以我想可能是最好的方法,谢谢 :)
    猜你喜欢
    • 1970-01-01
    • 2011-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多