郑昀 201005 隶属于《07.杂项》
关于上节《02-Twisted 构建 Web Server 的 Socket 长链接问题》,还可以继续探讨为何会保持 Socket 长链接。
有人在twisted邮件列表中也反映:
『We close the render_POST with a request.write('data') & a request.finish() but the connection stays open』调用了request.finish(),socket连接依然保持着,这现象和我们遇到的一样。
在 twisted.web.server 里,Request 的 finish 函数是这么定义的:
def finish(self):
http.Request.finish(self)
for d in self.notifications:
d.callback(None)
self.notifications = []
它调用了 twisted.web.http 的 Request 类之 finish 方法:
def finish(self):
"""We are finished writing data."""
if self.finished:
warnings.warn("Warning! request.finish called twice.", stacklevel=2)
return
if not self.startedWriting:
# write headers
self.write('')
if self.chunked:
# write last chunk and closing CRLF
self.transport.write("0\r\n\r\n")
# log request
if hasattr(self.channel, "factory"):
self.channel.factory.log(self)
self.finished = 1
if not self.queued:
self._cleanup()
可以看出 request.finish() 只是说要结束写数据了,把缓冲区内的数据都发送给对方,并没有去断开连接,否则就应该主动执行 self.transport.loseConnection() 。所以不主动断开socket连接也是设计使然。
本来 PubSubHubbub 的 Hub 本来就是要保持长连接,从而重用连接。它的文档 PublisherEfficiency 上称:
『HTTP persistent connections and pipelining
By default in HTTP 1.1, TCP connections will be reused between requests. For a publisher serving many separate Atom feeds, this allows Hubs to get around the expense of creating a new TCP connection every time an event happens. Instead, Hubs MAY leave their TCP connections open and reuse them to make HTTP requests for freshly published events. 』
twisted.web.server 是支持 support persistent connections and pipelining 的。
文档指出:
『Alternatively, they can return a special constant,twisted.web.server.NOT_DONE_YET, which tells the web server not to close the connection』即通过返回一个NOT_DONE_YET来告知Web Server不要关闭socket连接。
所以会看到 Google Code 上不少代码都是在 render 函数内返回 server.NOT_DONE_YET 。
twisted.web.server.py 中, Request.render 函数定义中有这么一句判断:
if body == NOT_DONE_YET:
return
...
self.finish()
也就是说如果你返回 NOT_DONE_YET ,就不会再调用 request.finish() 了。
这个 NOT_DONE_YET 标志有几种解释:
http://www.olivepeak.com/blog/posts/read/simple-http-pubsub-server-with-twisted 说:
『returns server.NOT_DONE_YET. This tells Twisted to not return anything to the client, but leave the connection open for later processing.』
http://www.ibm.com/developerworks/library/l-twist2.html?S_TACT=105AGX52&S_CMP=cn-a-l 则认为:
『The odd-looking return value server.NOT_DONE_YET is a flag to the Twisted server to flush the page content out of the request object.』
http://blog.csdn.net/gashero/archive/2007/03/02/1519045.aspx 说:
『你可以使用魔术值 twisted.web.server.NOT_DONE_YET ,可以告知Resource有些事情是异步的而且尚未完成,直到你调用了request.finish()。然后调用request.finish()直到写完了所有的响应数据。』
总之,不管如何解释,return server.NOT_DONE_YET from the render_GET/render_POST method, so the connection keeps open是没错的。
所以身为 PubSubHubbub Subscriber 角色的 Web Server ,我们的 render_POST 方法可以这么写:
# 处理 PubSubHubbub Hub 推送过来的数据
def render_POST(self, request):
try:
body = request.content.read()
def finish_success(request):
if(request._disconnected or request.finished):
print('***>This request is already finished.')
pass
else:
print('***>This request wiil be finished.')
request.setResponseCode(http.NO_CONTENT)
request.write("")
request.finish()
def finish_failed(request):
print('-=->fail in parseData?')
threads.deferToThread(self.parseData, body).addCallbacks(lambda x: finish_success(request), lambda x: finish_failed(request))
"""deferToThread 方法默认用 reactor.getThreadPool() 开辟的线程池。
它调用这个线程池的 threadpool.callInThreadWithCallback
方法,实际效果和 reactor.callInThread 一样。区别只是 deferToThread 可以返回一个 deferred ,能够 addCallback。
"""
except:
traceback.print_exc()
request.setResponseCode(http.NO_CONTENT)#即204
return NOT_DONE_YET
也就是,接收到 POST 过来的数据后,异步扔给另一个方法解析和处理,这厢立刻 return NOT_DONE_YET , 等处理成功了,回调里再 finish 掉当前的 request 。