【问题标题】:How to shutdown a timed out http POST using urlopen by urllib2 in Python?如何在 Python 中使用 urllib2 的 urlopen 关闭超时的 http POST?
【发布时间】:2011-12-23 16:13:20
【问题描述】:

概述

我正在使用 Python 2.7.1 urllib2 包中的 urlopen 从 Windows XP 机器到远程 Apache 网络服务器(例如 Mac OS X 的内置网络共享)执行 HTTP POST。发送的数据包含一些标识符、数据和校验和,如果发送了所有数据,则服务器以确认响应。数据中的校验和可用于检查是否一切正常。

问题

通常这很好用,但有时互联网连接不好,通常是因为发送数据的客户端使用 wifi 或 3G 连接。这会导致互联网连接丢失一段任意时间。 urlopen 包含一个超时选项,以确保这不会阻止您的程序并且可以继续。

这就是我想要的,但问题是 urlopen 不会阻止套接字继续发送超时发生时它仍然必须发送的任何数据。我已经通过尝试向我的笔记本电脑发送大量数据来测试这个(使用我将在下面显示的代码),我会在两个显示活动中看到网络活动,然后我会停止笔记本电脑上的无线,等待直到功能超时,然后重新激活无线,然后数据传输将继续,但程序将不再监听响应。我什至尝试退出 Python 解释器,但它仍然会发送数据,因此控制权会以某种方式移交给 Windows。

原因

超时(据我所知)是这样工作的: 它检查“空闲响应时间”
([Python-Dev] Adding socket timeout to urllib2)
如果将超时设置为 3,它将打开连接,启动计数器,然后尝试发送数据并等待响应,如果在收到响应之前的任何时间点计时器耗尽,则会调用超时异常。请注意,就超时计时器而言,数据的发送似乎不算作“活动”。
(urllib2 times out but doesn't close socket connection)
(Close urllib2 connection)

显然,当一个套接字被关闭/取消引用/垃圾收集时,它会调用它的“关闭”函数,该函数等待所有数据在关闭套接字之前发送。但是还有一个关闭功能,它应该立即停止套接字,防止发送更多数据。
(socket.shutdown vs socket.close)
(http://docs.python.org/library/socket.html#socket.socket.close)

我想要什么

我希望在发生超时时“关闭”连接。否则我的客户将无法判断数据是否正确接收,它可能会尝试再次发送。我宁愿直接终止连接并稍后再试,因为知道数据(可能)没有成功发送(如果校验和不匹配,服务器可以识别这一点)。

这是我用来测试的部分代码。 try..except 部分还没有像我预期的那样工作,任何帮助也很感激。正如我之前所说,我希望程序在引发超时(或任何其他)异常后立即关闭套接字。

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

【问题讨论】:

  • 一个想法是在客户端创建一个超时管理器,这样说:如果我在 x 秒内没有从服务器返回成功状态,我将关闭现有连接并稍后重试。
  • @Hoff 这就是 urlopen 中的超时应该做的,或者至少这是我所期望的。但是它不会关闭现有的连接,它会继续在后台运行,即使 python 完全退出。我不知道如何(如果可能的话)制作一个单独的程序来查找这些超时连接并关闭它们,你如何引用它们?我认为的主要问题是,当发生超时异常时,您无法再访问 urlopen 的套接字以将其关闭。

标签: python sockets timeout http-post urllib2


【解决方案1】:

事实证明,在上传的 HTTPConnection 上调用 .sock.shutdown(socket.SHUT_RDWR) 和 .close() 命令不会停止上传。它将继续在后台运行。在使用 urllib2 或 httplib 时,我不知道有更可靠/直接的方法来终止来自 Python 的连接。
最后,我们使用 urllib2 测试了上传,没有超时。这意味着在慢速连接上,上传(POST)可能需要很长时间,但至少我们会知道它是否有效。 urlopen 有可能因为没有超时而挂起,但我们已经测试了各种连接不良的可能性,在所有情况下,urlopen 要么正常工作,要么在一段时间后返回错误。
这意味着我们至少会在客户端知道上传成功或失败,并且不会在后台继续。

【讨论】:

    【解决方案2】:

    您可以考虑使用与 urllib2 不同的 API。 httplib 不那么令人愉快,但通常还不错。但是,它确实使您可以访问底层的套接字对象。因此,您可以执行以下操作:

    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 缓冲区,即使它的方法并不总是完全最优......

    【讨论】:

    • 我尝试使用 httplib,使用 except 以在超时的情况下关闭(并关闭)。在测试期间,python 运行了关闭和关闭命令并退出了我的脚本,但在后台上传仍在继续。所以“关闭”似乎不如名字所暗示的有效。最后,我们接受了您在最后一行中提出的建议。我们一起删除了超时,让操作系统/Apache 来处理它,这似乎效果最好。
    【解决方案3】:

    如果调用socket.shutdown 真的是在超时时切断数据的唯一方法,我认为你需要求助于某种猴子补丁。 urllib2 并没有真正为您提供这种细粒度套接字控制的机会。

    查看Source interface with Python and urllib2 了解一个好的方法。

    【讨论】:

    • 我尝试使用关闭(使用 httplib),但即使在调用 .shutdown(SHUT_RDWR) 和 .close() 之后数据仍然在流动。
    【解决方案4】:

    您可以使用multiprocessing 生成辅助线程,然后在检测到超时时将其关闭(URLError 异常,消息“urlopen 错误超时”)。

    停止进程应该足以关闭套接字。

    【讨论】:

    • 但是停止哪个进程?我试图退出 Python 解释器,但它仍会继续发送数据,因此将控制权交给某个 Windows 缓冲区。
    【解决方案5】:

    我找到了一些可能对你有帮助的代码on this thread:

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

    【讨论】:

    • 谢谢,这看起来很有趣。我目前无法对其进行测试,但我会尽可能地尝试(我对 Python 很陌生)。我仍然不知道这是否会关闭套接字,我不应该调用 fh.fp._sock.fp._sock.shutdown(socket.SHUT_RDWR) (如 [close-urllib2-connection])而不是 fh 。关闭()?也许我不明白,但是 urlopen(url, data) 调用不是打开套接字(连接),然后发送数据然后等待响应吗?所以在我启动计时器之前,我必须等待它完成吗? Timer不应该在urlopen之前启动吗?
    猜你喜欢
    • 2015-02-05
    • 2012-03-07
    • 2011-03-31
    • 1970-01-01
    • 2011-01-06
    • 2013-04-07
    • 2019-03-11
    • 2012-04-11
    • 2012-11-24
    相关资源
    最近更新 更多