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

时间:2011-11-07 14:21:06

标签: python sockets timeout http-post urllib2

概述

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

问题

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

这就是我想要的,但问题是urlopen不会阻止套接字继续发送超时发生时仍然必须发送的任何数据。我通过尝试向我的笔记本电脑发送大量数据来测试这个(我将在下面显示的代码),我会在两个show activity上看到网络活动,然后我会在笔记本电脑上停止无线,等待直到该功能超时,然后重新激活无线,然后数据传输将继续,但程序将不再监听响应。我甚至试图退出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()

5 个答案:

答案 0 :(得分:1)

我找到了一些可能对您有帮助的代码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()

答案 1 :(得分:1)

您可以考虑使用与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缓冲区但它想要的,即使它的方法并不总是完全最优......

答案 2 :(得分:0)

如果调用socket.shutdown确实是切断超时数据的唯一方法,我认为你需要采取某种猴子修补方式。 urllib2并没有真正为你提供那种细粒度套接字控制的机会。

查看Source interface with Python and urllib2以获得良好的方法。

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

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