如何在Python中使用urllib2的urlopen关闭超时的HTTP POST?
概述
我在使用Python 2.7.1的urllib2库里的urlopen功能,从一台Windows XP电脑向远程的Apache服务器(比如Mac OS X自带的网页分享功能)发送HTTP POST请求。发送的数据里包含一些标识符、数据和一个校验和。如果所有数据都发送成功,服务器会回复一个确认信息。校验和可以用来检查数据是否完整到达。
问题
通常这个过程很顺利,但有时候网络连接不太好,尤其是当发送数据的设备使用WiFi或3G连接时。这会导致网络连接在某个随机的时间段内中断。urlopen有一个超时选项,可以确保程序不会因为这个问题而卡住,能够继续运行。
这是我想要的,但问题在于,当超时发生时,urlopen并不会停止socket继续发送它还没发送完的数据。我通过下面的代码测试过,尝试向我的笔记本发送大量数据。我能看到网络活动在两台设备上都有显示,然后我会关闭笔记本的无线网络,等到函数超时,再重新开启无线网络,这时数据传输会继续,但程序就不再接收回复了。我甚至尝试退出Python解释器,数据仍然会继续发送,所以控制权似乎是交给Windows了。
原因
根据我的理解,超时是这样工作的:
它会检查一个“空闲响应时间”。
如果你把超时设置为3秒,它会打开连接,开始计时,然后尝试发送数据并等待回复。如果在收到回复之前计时器到期,就会触发一个超时异常。需要注意的是,发送数据在超时计时器看来似乎不算“活动”。
据说,当一个socket被关闭、解除引用或垃圾回收时,它会调用自己的“关闭”函数,这个函数会等待所有数据发送完再关闭socket。然而,还有一个shutdown函数,应该可以立即停止socket,防止再发送任何数据。
我想要的
我希望在发生超时时,连接能够被“关闭”。否则我的客户端就无法判断数据是否被正确接收,可能会尝试重新发送。我宁愿直接关闭连接,稍后再试,知道数据(可能)没有成功发送(服务器可以通过校验和不匹配来识别这一点)。
以下是我用来测试的部分代码。try..except部分目前还没有按我预期的工作,任何帮助都很感激。正如我之前所说,我希望程序在超时(或其他)异常发生时立即关闭socket。
from urllib import urlencode
from urllib2 import urlopen, HTTPError, URLError
import socket
import sys
class Uploader:
def __init__(self):
self.URL = "http://.../"
self.data = urlencode({'fakerange':range(0,2000000,1)})
print "Data Generated"
def upload(self):
try:
f = urlopen(self.URL, self.data, timeout=10)
returncode = f.read()
except (URLError, HTTPError), msg:
returncode = str(msg)
except socket.error:
returncode = "Socket Timeout!"
else:
returncode = 'Im here'
def main():
upobj = Uploader()
returncode = upobj.upload()
if returncode == '100':
print "Success!"
else:
print "Maybe a Fail"
print returncode
print "The End"
if __name__ == '__main__':
main()
5 个回答
你可以考虑使用一个不同的API,而不是urllib2。httplib虽然用起来稍微不太方便,但也还算可以。它的好处是,你可以直接访问底层的socket对象。所以,你可以这样做:
import httplib
import socket
def upload(host, path, data):
conn = httplib.HTTPConnection(host, 80, True, 3)
try:
conn.request('POST', path, data)
response = conn.getresponse()
if response.status != 200:
# maybe an HTTP error
return response.status
else:
response_data = r.read()
return response_data
except socket.error:
return "Socket Timeout!"
finally:
conn.sock.shutdown()
conn.close()
def main():
data = urlencode({'fakerange':range(0,2000000,1)})
returncode = upload("www.server.com", "/path/to/endpoint", data)
...
(免责声明:未经测试)
跟urllib2比起来,httplib确实有一些限制,比如它不会自动处理重定向等情况。不过,如果你是用它来访问一个相对固定的API,而不是从网上下载随机的东西,那它应该能很好地完成任务。
老实说,我自己可能不会去这么做;我一般比较满意让操作系统自己处理TCP缓冲区,尽管它的处理方式不一定总是最优的……
我找到了一些代码,可能对你有帮助,你可以在这个讨论串里查看:
from urllib2 import urlopen
from threading import Timer
url = "http://www.python.org"
def handler(fh):
fh.close()
fh = urlopen(url)
t = Timer(20.0, handler,[fh])
t.start()
data = fh.read()
t.cancel()
结果发现,在一个正在上传的 HTTPConnection 上调用 .sock.shutdown(socket.SHUT_RDWR) 和 .close() 命令并不能停止上传。上传会继续在后台进行。我不知道在使用 urllib2 或 httplib 时,有没有更可靠或直接的方法来终止连接。
最后,我们测试了在没有设置超时的情况下使用 urllib2 进行上传。这意味着在网络慢的时候,上传(POST)可能会花很长时间,但至少我们能知道上传是否成功。虽然没有超时设置可能会导致 urlopen 卡住,但我们测试了各种糟糕的网络情况,结果是 urlopen 要么成功,要么在一段时间后返回错误。
这意味着我们至少能在客户端知道上传是成功还是失败,并且它不会在后台继续进行。