如何用Twisted的getPage替代urlopen?
我想在一个网页应用中使用Twisted的非阻塞getPage方法,但感觉这个方法用起来比urlopen要复杂很多。
这是我想要实现的一个例子:
def web_request(request):
response = urllib.urlopen('http://www.example.org')
return HttpResponse(len(response.read()))
难道要用getPage就这么难吗?
2 个回答
我最近在一个类似的问题上发表了一个回复,里面提供了使用getPage
从网址获取内容所需的最基本代码。为了完整性,这里再贴一下:
from twisted.web.client import getPage
from twisted.internet import reactor
url = 'http://aol.com'
def print_and_stop(output):
print output
if reactor.running:
reactor.stop()
if __name__ == '__main__':
print 'fetching', url
d = getPage(url)
d.addCallback(print_and_stop)
reactor.run()
请记住,你可能需要更深入地了解Twisted使用的反应器模式,这个模式用来处理事件(在这个例子中,getPage
的触发就是一个事件)。
关于非阻塞操作(你似乎特别想要的那种),需要明白的是,你不能用它们写出顺序执行的代码。非阻塞的意思是它们不会等待结果,而是启动操作后就把控制权交回给你的函数。所以,getPage
不会像 urllib.urlopen
那样返回一个可以直接读取的文件对象。即使它返回了,你也不能在数据还没准备好之前读取它(那样会造成阻塞)。因此,你不能在它上面调用 len()
,因为这需要先读取所有数据(这会阻塞)。
在 Twisted 中处理非阻塞操作的方法是使用 Deferreds
,这是一种管理回调的对象。getPage
返回一个 Deferred
,这意味着“你稍后会得到这个结果”。在你得到结果之前,你不能对它做任何事情,所以你需要给 Deferred
添加一些 回调,当结果准备好时,Deferred
会调用这些回调。那个回调可以执行你想要的操作:
def web_request(request)
def callback(data):
HttpResponse(len(data))
d = getPage("http://www.example.org")
d.addCallback(callback)
return d
你例子中的另一个问题是你的 web_request
函数本身是阻塞的。在你等待 getPage
的结果时,你想做什么?是在 web_request
中做其他事情,还是只是等待?或者你想让 web_request
本身变成非阻塞的?如果是这样,你想怎么产生结果?(在 Twisted 中,明显的选择是返回另一个 Deferred
,甚至可以返回和 getPage
返回的同一个。这在你用其他框架写代码时可能不太合适。)
确实有一种方法可以使用 Deferreds
写出顺序执行的代码,虽然这有点限制,调试起来也更困难,而且 Twisted 的核心开发者看到你用它时会感到痛苦:twisted.internet.defer.inlineCallbacks
。它利用了 Python 2.5 中的新生成器特性,你可以把数据发送到生成器中,代码看起来大致是这样的:
@defer.inlineCallbacks
def web_request(request)
data = yield getPage("http://www.example.org")
HttpResponse(len(data))
就像那个明确返回 d
的 Deferred
的例子一样,这只有在调用者期望 web_request
是非阻塞的情况下才有效——defer.inlineCallbacks
装饰器会把生成器变成一个返回 Deferred
的函数。