Сила python mechanize/urllib2 использовать только запросы A?
Вот связанный вопрос, но я не мог понять, как применить ответ на mechanize/urllib2: как заставить библиотеку httplib python использовать только A-запросы
В принципе, учитывая этот простой код:
#!/usr/bin/python
import urllib2
print urllib2.urlopen('http://python.org/').read(100)
Это приводит к тому, что прокси-сервер говорит следующее:
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 или механизировать использовать любые сокеты, которые я создаю.
РЕДАКТИРОВАТЬ. Я знаю, что вопросы AAAA являются проблемой, потому что в других приложениях также была задержка, и как только я перекомпилировал с отключенным ipv6, проблема исчезла... кроме как в python который все еще выполняет запросы AAAA.
Ответы
Ответ 1
Страдая от той же проблемы, вот уродливый взлом (используйте на свой страх и риск..) на основе информации, предоставленной J.J.,
Это в основном заставляет параметр family
от socket.getaddrinfo(..)
до 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)
Это работает для меня, по крайней мере, в этом простом случае.
Ответ 2
Нет ответа, но несколько данных. Похоже, что разрешение DNS происходит от httplib.py
в HTTPConnection.connect()
(строка 670 на моем python 2.5.4 stdlib)
Поток кода примерно:
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()
приведет к разрешению имен - в моем ящике OS X с включенным IPv6, как записи A, так и AAAA, оба ответа возвращаются назад и оба возвращаются.
-
остальная часть цикла подключения пытается вернуть каждый возвращенный адрес до тех пор, пока не будет выполнено
Например:
>>> 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()
жестко закодировано до нуля, вы не сможете переопределить записи A или AAAA через некоторый поддерживаемый интерфейс API в urllib. Если механизация не делает свое собственное разрешение имени по какой-то другой причине, механизация тоже не может. Из конструкции контура подключения это By Design.
-
python socket module - тонкая оболочка вокруг API-интерфейсов сокетов POSIX; Я ожидаю, что они разрешат каждую семью, доступную и настроенную в системе. Дважды проверьте конфигурацию Gentoo IPv6.
Ответ 3
DNS-сервер 8.8.8.8 (DNS-сервер Google) сразу отвечает на вопрос об AAAA python.org. Таким образом, тот факт, что мы не видим этот ответ в трассировке, которую вы публикуете, вероятно, указывает на то, что этот пакет не вернулся (что происходит с 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
Ответ 4
Скорее всего, причиной этого является неработающий брандмауэр. Брандмауэры Juniper могут вызвать это, например, хотя у них есть обходной путь.
Если вы не можете заставить администраторов сети исправлять брандмауэр, вы можете попробовать обходное решение на основе хоста. Добавьте эту строку в свой /etc/resolv.conf
:
options single-request-reopen
Страница руководства хорошо объясняет:
Резольвер использует тот же сокет для запросов A и AAAA. Некоторое оборудование ошибочно возвращает только один ответ. Когда это произойдет, клиентская система будет сидеть и ждать второго ответа. Включение этой опции при изменении этого поведения, так что если два запроса с одного и того же порта не будут обработаны правильно, он закроет сокет и откроет новый, прежде чем отправлять второй запрос.