在twisted中实现重新连接http客户端

发布于 2024-11-28 15:38:38 字数 3306 浏览 0 评论 0原文

我正在实现一个库模块作为 couchdb 更改通知的客户端。我想让库处于“连续”模式,也就是说,连接必须永远保持打开状态,或者至少如果连接已关闭,它必须重新连接,以便couchdb有一个通道来通知库中发生的任何新更改数据库。然后我将处理这些通知以生成某些事件(这尚未实现)。

我选择的方法是使用 ReconnectingClientFactory (它根据详细的算法自动重新连接)作为我的协议工厂的基础。每当建立连接时,都会调用 buildProtocol 方法。在此方法中,我创建协议实例,并(立即)触发 callLater 以表示连接已准备就绪。在 cdConnected 函数中,我发送请求并添加回调来处理接收到的数据 (cbReceived)。

代码按预期重新连接,但我遇到两个不同的问题:

  • 请求失败(没有通过 tcp 连接发送数据),但我不知道为什么。
  • 即使连接完全关闭,也会生成错误。

也许有人知道我做错了什么?

谢谢!

(编辑:“连接已完全关闭。”错误是由我自己打印的,因此可以忽略)

这是代码:

from   twisted.internet              import defer
from   twisted.internet.protocol     import ReconnectingClientFactory
from   twisted.web._newclient        import HTTP11ClientProtocol
from   twisted.web._newclient        import Request
from   twisted.web.client            import _parse

class MyReconnectingClientFactory(ReconnectingClientFactory):

    def __init__(self, reactor, cbConnected):
        self.reactor      = reactor
        self.cbConnected  = cbConnected

    def startedConnecting(self, connector):
        print 'Started to connect ...'

    def buildProtocol(self, addr):
        print 'Resetting reconnection delay'
        self.resetDelay()
        proto = HTTP11ClientProtocol()
        self.reactor.callLater(0, self.cbConnected, proto)
        return proto

    def clientConnectionLost(self, connector, reason):
        print 'Lost connection.  Reason:', reason
        ReconnectingClientFactory.clientConnectionLost(self, connector, reason)

    def clientConnectionFailed(self, connector, reason):
        print 'Connection failed. Reason:', reason
        ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)

def cbReceived(response):
    print response

def printError(failure):
    print "printError > %s" % (str(failure))

def cbConnected(proto):
    print "Sending request ..."
    req = Request(method, path, headers, bodyProducer)
    d = proto.request(req)
    d.addCallback(cbReceived).addErrback(printError)
    return d

from twisted.internet import reactor

uri='http://localhost:5984/cn/_changes?feed=continuous'
method='GET'
headers=None
bodyProducer=None

scheme, host, port, path = _parse(uri)
factory = MyReconnectingClientFactory(reactor, cbConnected)
reactor.connectTCP(host, port, factory)
reactor.run()

这是输出:

Started to connect ...
Resetting reconnection delay
Sending request ...
printError > [Failure instance: Traceback (failure with no frames): <class 'twisted.web._newclient.RequestGenerationFailed'>: [<twisted.python.failure.Failure <type 'exceptions.AttributeError'>>]
]
Lost connection.  Reason: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
]
Started to connect ...
Resetting reconnection delay
Sending request ...
printError > [Failure instance: Traceback (failure with no frames): <class 'twisted.web._newclient.RequestGenerationFailed'>: [<twisted.python.failure.Failure <type 'exceptions.AttributeError'>>]
]
Lost connection.  Reason: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
]

I am implementing a library module to serve as client for couchdb change notifications. I want to have the library in "continuous" mode, that is, the connection must remain open forever, or at least it must reconnect if the connection has been closed, so that couchdb has a channel to notify of any new changes happening in the database. I will then process those notifications to generate certain events (this is not yet implemented).

The approach that I have chosen is to use the ReconnectingClientFactory (which does automatic re-connection as per an elaborated algorithm) as basis for my Protocol Factory. Whenever the connection has been established, the buildProtocol method is called. In this method I create the protocol instance, and fire a callLater (immediately) to signal that the connection is ready. In the cdConnected function, I send the request and add a callback to process the received data (cbReceived).

The code does reconnection as expected, but I am having two different problems:

  • the request is failing (no data is sent over the tcp connection), but I do not know why.
  • an error is generated, even though the connection is closed cleanly.

Maybe somebody has an idea of what I am doing wrong?

Thanks!

(edit: the "Connection was closed cleanly." error is being printed by myself, so it can be ignored)

Here is the code:

from   twisted.internet              import defer
from   twisted.internet.protocol     import ReconnectingClientFactory
from   twisted.web._newclient        import HTTP11ClientProtocol
from   twisted.web._newclient        import Request
from   twisted.web.client            import _parse

class MyReconnectingClientFactory(ReconnectingClientFactory):

    def __init__(self, reactor, cbConnected):
        self.reactor      = reactor
        self.cbConnected  = cbConnected

    def startedConnecting(self, connector):
        print 'Started to connect ...'

    def buildProtocol(self, addr):
        print 'Resetting reconnection delay'
        self.resetDelay()
        proto = HTTP11ClientProtocol()
        self.reactor.callLater(0, self.cbConnected, proto)
        return proto

    def clientConnectionLost(self, connector, reason):
        print 'Lost connection.  Reason:', reason
        ReconnectingClientFactory.clientConnectionLost(self, connector, reason)

    def clientConnectionFailed(self, connector, reason):
        print 'Connection failed. Reason:', reason
        ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)

def cbReceived(response):
    print response

def printError(failure):
    print "printError > %s" % (str(failure))

def cbConnected(proto):
    print "Sending request ..."
    req = Request(method, path, headers, bodyProducer)
    d = proto.request(req)
    d.addCallback(cbReceived).addErrback(printError)
    return d

from twisted.internet import reactor

uri='http://localhost:5984/cn/_changes?feed=continuous'
method='GET'
headers=None
bodyProducer=None

scheme, host, port, path = _parse(uri)
factory = MyReconnectingClientFactory(reactor, cbConnected)
reactor.connectTCP(host, port, factory)
reactor.run()

And here is the output:

Started to connect ...
Resetting reconnection delay
Sending request ...
printError > [Failure instance: Traceback (failure with no frames): <class 'twisted.web._newclient.RequestGenerationFailed'>: [<twisted.python.failure.Failure <type 'exceptions.AttributeError'>>]
]
Lost connection.  Reason: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
]
Started to connect ...
Resetting reconnection delay
Sending request ...
printError > [Failure instance: Traceback (failure with no frames): <class 'twisted.web._newclient.RequestGenerationFailed'>: [<twisted.python.failure.Failure <type 'exceptions.AttributeError'>>]
]
Lost connection.  Reason: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
]

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

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

发布评论

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

评论(1

黎夕旧梦 2024-12-05 15:38:38

您应该查看生成的故障。 _newclient 有抛出复合失败的习惯;这些包含进一步的失败。

我稍微调整了你的代码:

def printError(failure):
print "printError > %r" % failure

from twisted.web import _newclient
if failure.check(_newclient.RequestGenerationFailed):
    print "printError: RequestGenerationFailed"
    for f in failure.value.reasons:
        print "printError > %r" % f
        print f.getTraceback()

你在失败时执行了 str() ,这不是获取有关异常信息的好方法。让 repr 来处理它,这就是它的工作。

使用 %r,我发现它实际上给了我一个 RequestGenerationFailed。这是一个更有趣的失败。失败值是有原因的。

经过我的修改,脚本给出了以下内容:

Started to connect ...
Resetting reconnection delay
Sending request ...
printError > <twisted.python.failure.Failure <class 'twisted.web._newclient.RequestGenerationFailed'>>
printError: RequestGenerationFailed
printError > <twisted.python.failure.Failure <type 'exceptions.AttributeError'>>
Traceback (most recent call last):
  File "/usr/lib64/python2.7/site-packages/twisted/internet/base.py", line 1174, in mainLoop
    self.runUntilCurrent()
  File "/usr/lib64/python2.7/site-packages/twisted/internet/base.py", line 796, in runUntilCurrent
    call.func(*call.args, **call.kw)
  File "so.py", line 50, in cbConnected
    d = proto.request(req)
  File "/usr/lib64/python2.7/site-packages/twisted/web/_newclient.py", line 1266, in request
    _requestDeferred = maybeDeferred(request.writeTo, self.transport)
--- <exception caught here> ---
  File "/usr/lib64/python2.7/site-packages/twisted/internet/defer.py", line 125, in maybeDeferred
    result = f(*args, **kw)
  File "/usr/lib64/python2.7/site-packages/twisted/web/_newclient.py", line 703, in writeTo
    self._writeHeaders(transport, None)
  File "/usr/lib64/python2.7/site-packages/twisted/web/_newclient.py", line 535, in _writeHeaders
    hosts = self.headers.getRawHeaders('host', ())
exceptions.AttributeError: 'NoneType' object has no attribute 'getRawHeaders'

Lost connection.  Reason: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
]

这应该为您提供一个很好的线索,告诉您在哪里寻找实际问题。

顺便说一句,看看 Paisley,CouchDB 的 Twisted 客户端: paisley

有几个分支,并且特别是 我的更改分支 有一些您可能会收到的更改通知内容我制作了一个桌面小程序,向我显示添加到基于 CouchDB 的待办事项系统中的任务。

听起来您的更改 a) 已经在那里或 b) 应该进入那里; c) 您应该考虑与佩斯利合作并为其做出贡献。

祝你好运!

You should take a look at the failures generated. _newclient has a habit of throwing compound failures; these contain further failures.

I adapted your code a little:

def printError(failure):
print "printError > %r" % failure

from twisted.web import _newclient
if failure.check(_newclient.RequestGenerationFailed):
    print "printError: RequestGenerationFailed"
    for f in failure.value.reasons:
        print "printError > %r" % f
        print f.getTraceback()

You were doing a str() on failure and that's not a good way to get information about an exception. Let repr take care of it, that's its job.

Using %r, I saw that it was actually giving me a RequestGenerationFailed. That's a more interesting failure. The failure value has reasons.

With my modification, the script gave this:

Started to connect ...
Resetting reconnection delay
Sending request ...
printError > <twisted.python.failure.Failure <class 'twisted.web._newclient.RequestGenerationFailed'>>
printError: RequestGenerationFailed
printError > <twisted.python.failure.Failure <type 'exceptions.AttributeError'>>
Traceback (most recent call last):
  File "/usr/lib64/python2.7/site-packages/twisted/internet/base.py", line 1174, in mainLoop
    self.runUntilCurrent()
  File "/usr/lib64/python2.7/site-packages/twisted/internet/base.py", line 796, in runUntilCurrent
    call.func(*call.args, **call.kw)
  File "so.py", line 50, in cbConnected
    d = proto.request(req)
  File "/usr/lib64/python2.7/site-packages/twisted/web/_newclient.py", line 1266, in request
    _requestDeferred = maybeDeferred(request.writeTo, self.transport)
--- <exception caught here> ---
  File "/usr/lib64/python2.7/site-packages/twisted/internet/defer.py", line 125, in maybeDeferred
    result = f(*args, **kw)
  File "/usr/lib64/python2.7/site-packages/twisted/web/_newclient.py", line 703, in writeTo
    self._writeHeaders(transport, None)
  File "/usr/lib64/python2.7/site-packages/twisted/web/_newclient.py", line 535, in _writeHeaders
    hosts = self.headers.getRawHeaders('host', ())
exceptions.AttributeError: 'NoneType' object has no attribute 'getRawHeaders'

Lost connection.  Reason: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
]

That should give you a good clue on where to look for your actual problem.

Incidentally, take a look at Paisley, a Twisted client for CouchDB: paisley

There are a few branches, and in particular my changes branch has some change notification stuff you may be interested in. I made a desktop applet that showed me tasks added to my CouchDB-based todo system.

It sounds like your changes are a) already in there or b) should go in there; and c) you should consider working with and contributing to Paisley.

Good luck!

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