11


8

python mechanize / urllib2でAリクエストのみを使用するように強制しますか?

関連する質問がありますが、答えをmechanize / urllib2に適用する方法がわかりませんでした:https://stackoverflow.com/questions/1540749/how-to-force-python-httplib-library-to-use-only -a-requests [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を強制する方法や、作成したソケットを使用するように機械化する方法はわかりません。

編集:AAAAクエリが問題であることは知っています。他のアプリにも遅延があり、ipv6を無効にして再コンパイルするとすぐに問題はなくなりました…​ まだAAAAリクエストを実行するPythonを除きます。

4 Answer


15


同じ問題に苦しんでいるのは、J.J。から提供された情報に基づいたいハックです(ご自身の責任で使用してください。)。 .

これは基本的に、 socket.AF_UNSPEC(ゼロ、` socket.create_connection`で使用されているようです)を使用する代わりに、 socket.getaddrinfo(..)`の `family`パラメーターを socket.AF_INET`に強制します。 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


答えはありませんが、いくつかのデータポイント。 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()`の3番目の引数はソケットを制限します ファミリー-すなわち、IPv4 vs. IPv6 ゼロを渡すと、すべてのファミリが返されます。 ゼロはstdlibにハードコードされています。

  • ホスト名を `getaddrinfo()`に渡すと、名前解決が発生します- IPv6を有効にしたOS Xボックスでは、AレコードとAAAAレコードの両方が消え、両方の回答がすぐに返され、両方が返されます。

  • 接続ループの残りは、返される各アドレスを1つまで試行します 成功する

例えば:

>>> 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は​​、urllibでサポートされているAPIインターフェイスを介して記録します。 他の理由で機械化が独自の名前解決を行わない限り、機械化もできません。 接続ループの構造から、これは設計によるものです。

  • pythonのソケットモジュールは、POSIXソケットAPIの薄いラッパーです。 私は_システム上で利用可能に設定されたすべてのファミリーを解決しています。 GentooのIPv6構成を再確認してください。


2


DNSサーバー8.8.8.8(Google DNS)は、python.orgのAAAAについて尋ねるとすぐに応答します。 したがって、投稿したトレースにこの応答が表示されないという事実は、おそらくこのパケットが戻ってこなかったことを示しています(これはUDPで発生します)。 この損失がランダムな場合、それは正常です。 体系的である場合は、ネットワーク設定に問題があることを意味します。ファイアウォールが壊れている可能性があり、最初のAAAA応答が返ってこない可能性があります。

5秒の遅延は、スタブリゾルバから発生します。 その場合、それがランダムであれば、おそらく不運ですが、IPv6とは関係ありません。Aレコードの応答も失敗した可能性があります。

IPv6を無効にすることは非常に奇妙な動きのようで、最後のIPv4アドレスが配布されるわずか2年前です!

% 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


2


この原因として最も可能性が高いのは、https://serverfault.com/q/411165/126632 [壊れた出力ファイアウォール]です。 たとえば、ジュニパーのファイアウォールは、http://kb.juniper.net/InfoCenter/index?page = content&id = KB12312&actp = RSS [回避策]が利用可能ですが、これを引き起こす可能性があります。

ネットワーク管理者にファイアウォールの修正を依頼できない場合は、ホストベースの回避策を試すことができます。 `/ etc / resolv.conf`に次の行を追加してください:

options single-request-reopen

マニュアルページで説明しています。

_ リゾルバーは、AおよびAAAA要求に同じソケットを使用します。 一部のハードウェアは誤って返信を1つしか送信しません。 それが起こると、クライアントシステムは座って、2番目の応答を待ちます。 このオプションをオンにすると、この動作が変更され、同じポートからの2つのリクエストが正しく処理されない場合、2番目のリクエストを送信する前にソケットが閉じて新しいソケットが開きます。 _