如何在Python中使用urllib2的urlopen关闭超时的HTTP POST?

7 投票
5 回答
2423 浏览
提问于 2025-04-17 05:47

概述

我在使用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 个回答

1

你可以考虑使用一个不同的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缓冲区,尽管它的处理方式不一定总是最优的……

1

我找到了一些代码,可能对你有帮助,你可以在这个讨论串里查看:

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()
0

结果发现,在一个正在上传的 HTTPConnection 上调用 .sock.shutdown(socket.SHUT_RDWR) 和 .close() 命令并不能停止上传。上传会继续在后台进行。我不知道在使用 urllib2 或 httplib 时,有没有更可靠或直接的方法来终止连接。
最后,我们测试了在没有设置超时的情况下使用 urllib2 进行上传。这意味着在网络慢的时候,上传(POST)可能会花很长时间,但至少我们能知道上传是否成功。虽然没有超时设置可能会导致 urlopen 卡住,但我们测试了各种糟糕的网络情况,结果是 urlopen 要么成功,要么在一段时间后返回错误。
这意味着我们至少能在客户端知道上传是成功还是失败,并且它不会在后台继续进行。

撰写回答