Заставить python mechanize / urllib2 использовать только запросы A? - PullRequest
11 голосов
/ 06 января 2010

Вот связанный вопрос, но я не мог понять, как применить ответ к mechanize / urllib2: как заставить библиотеку python httplib использовать только запросы A

В основном, учитываяэтот простой код:

#!/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 или механизировать использовать любые созданные мной сокеты.

EDIT : я знаю, что запросы AAAA являются проблемой, потому что другие приложения также имели задержкуи как только я перекомпилировал с отключенным ipv6, проблема исчезла ... за исключением python, который все еще выполняет запросы AAAA.

Ответы [ 4 ]

15 голосов
/ 12 июня 2011

Страдая от той же проблемы, вот уродливый хакер (используйте на свой страх и риск ..), основанный на информации, предоставленной 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)

Это работает для меня, по крайней мере, в этом простом случае.

4 голосов
/ 10 января 2010

Нет ответа, но есть несколько точек данных. Разрешение 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 - это тонкая оболочка для API-интерфейсов сокетов POSIX; Я ожидаю , что они разрешают каждую семью, доступную и настроенную в системе. Перепроверьте конфигурацию Gentoo IPv6.

2 голосов
/ 03 декабря 2012

Наиболее вероятной причиной этого является сломанный выходной брандмауэр . Брандмауэры Juniper могут вызывать это, например, хотя у них есть обходной путь .

Если вы не можете заставить своих сетевых администраторов починить брандмауэр, вы можете попробовать обходной путь на основе хоста. Добавьте эту строку к вашему /etc/resolv.conf:

options single-request-reopen

Справочная страница объясняет это хорошо:

распознаватель использует один и тот же сокет для запросов A и AAAA. Некоторое оборудование по ошибке отправляет только один ответ. Когда это произойдет, клиентская система будет сидеть и ждать второго ответа. Включение этой опции изменяет это поведение так, что, если два запроса с одного и того же порта обрабатываются неправильно, он закрывает сокет и открывает новый перед отправкой второго запроса.

2 голосов
/ 09 января 2010

DNS-сервер 8.8.8.8 (Google DNS) отвечает сразу же, когда его спрашивают об 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
...