强制python mechanize/urllib2只使用请求?

2024-05-16 01:25:05 发布

您现在位置:Python中文网/ 问答频道 /正文

这里有一个相关的问题,但是我不知道如何将答案应用到mechanize/urllib2:how to force python httplib library to use only A requests

基本上,给定这个简单的代码:

#!/usr/bin/python
import urllib2
print urllib2.urlopen('http://python.org/').read(100)

这导致wireshark说:

  0.000000  10.102.0.79 -> 8.8.8.8      DNS Standard query A python.org
  0.000023  10.102.0.79 -> 8.8.8.8      DNS Standard query AAAA python.org
  0.005369      8.8.8.8 -> 10.102.0.79  DNS Standard query response A 82.94.164.162
  5.004494  10.102.0.79 -> 8.8.8.8      DNS Standard query A python.org
  5.010540      8.8.8.8 -> 10.102.0.79  DNS Standard query response A 82.94.164.162
  5.010599  10.102.0.79 -> 8.8.8.8      DNS Standard query AAAA python.org
  5.015832      8.8.8.8 -> 10.102.0.79  DNS Standard query response AAAA 2001:888:2000:d::a2

这是一个5秒的延迟!

我的系统中没有启用IPv6(gentoo是用USE=-ipv6编译的),所以我认为python甚至没有任何理由尝试IPv6查找。

上面提到的问题建议显式地将套接字类型设置为AF_INET,这听起来不错。我不知道如何强制urllib或mechanize使用我创建的任何套接字。

编辑:我知道AAAA查询是问题所在,因为其他应用程序也有延迟,当我在禁用ipv6的情况下重新编译时,问题就消失了。。。除了在python中仍然执行AAAA请求。


Tags: to答案orgdnsresponseurllib2queryhttplib
3条回答

同样的问题,根据J.J.提供的信息,这里有一个丑陋的黑客攻击(风险自负)。

这基本上是将socket.getaddrinfo(..)family参数强制为socket.AF_INET,而不是使用socket.AF_UNSPEC(零,这似乎是在socket.create_connection中使用的),这不仅适用于来自urllib2的调用,而且也适用于对socket.getaddrinfo(..)的所有调用:

#--------------------
# do this once at program startup
#--------------------
import socket
origGetAddrInfo = socket.getaddrinfo

def getAddrInfoWrapper(host, port, family=0, socktype=0, proto=0, flags=0):
    return origGetAddrInfo(host, port, socket.AF_INET, socktype, proto, flags)

# replace the original socket.getaddrinfo by our version
socket.getaddrinfo = getAddrInfoWrapper

#--------------------
import urllib2

print urllib2.urlopen("http://python.org/").read(100)

至少在这个简单的例子中,这对我有效。

没有答案,只有几个数据点。DNS解析似乎来自HTTPConnection.connect()中的httplib.py(python 2.5.4 stdlib上的第670行)

代码流大致如下:

for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
    af, socktype, proto, canonname, sa = res
    self.sock = socket.socket(af, socktype, proto)
    try:
        self.sock.connect(sa)
    except socket.error, msg: 
        continue
    break

对发生的事情有几点看法:

  • socket.getaddrinfo()的第三个参数限制套接字系列,即IPv4与IPv6。通过零返回所有族。零被硬编码到stdlib中。

  • 将主机名传递到getaddrinfo()将导致名称解析——在启用了IPv6的OS X框中,a和AAAA记录都会输出,两个答案都会返回,并且都会返回。

  • connect循环的其余部分尝试每个返回的地址,直到一个成功为止

例如:

>>> socket.getaddrinfo("python.org", 80, 0, socket.SOCK_STREAM)
[
 (30, 1, 6, '', ('2001:888:2000:d::a2', 80, 0, 0)), 
 ( 2, 1, 6, '', ('82.94.164.162', 80))
]
>>> help(socket.getaddrinfo)
getaddrinfo(...)
    getaddrinfo(host, port [, family, socktype, proto, flags])
        -> list of (family, socktype, proto, canonname, sockaddr)

一些猜测:

  • 由于getaddrinfo()中的套接字系列硬编码为零,您将无法通过urllib中支持的一些API接口重写A与AAAA记录。除非mechanize出于其他原因执行自己的名称解析,否则mechanize也不能。从连接循环的构造来看,这是通过设计实现的。

  • python的socket模块是POSIX socket api的一个瘦包装器;我期望它们正在解析系统上配置的每个可用系列。仔细检查Gentoo的IPv6配置。

当被问及python.org的AAAA时,DNS服务器8.8.8.8(googledns)会立即回复。因此,我们在您发布的跟踪中没有看到此回复,这可能表明此数据包没有返回(这在UDP中发生)。如果这种损失是随机的,那是正常的。如果是系统性的,则意味着网络设置中存在问题,可能是防火墙断开,导致第一个AAAA回复无法返回。

5秒的延迟来自存根解析程序。在这种情况下,如果是随机的,可能是运气不好,但与IPv6无关,对A记录的回复也可能失败。

禁用IPv6似乎是一个非常奇怪的举动,就在最后一个IPv4地址被分发的两年前!

% dig @8.8.8.8  AAAA python.org

; <<>> DiG 9.5.1-P3 <<>> @8.8.8.8 AAAA python.org
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50323
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;python.org.                    IN      AAAA

;; ANSWER SECTION:
python.org.             69917   IN      AAAA    2001:888:2000:d::a2

;; Query time: 36 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Sat Jan  9 21:51:14 2010
;; MSG SIZE  rcvd: 67

相关问题 更多 >