How to debug urllib2 request (Python 2.7)?

Python公式ドキュメントは非常に良く出来ているんだけれども、稀にある、こういうの。

このサイトでは良く言っていることだけれども、Python 2.7 になってから Python が「劇的に」変わったのはそう、ドキュメントが Sphinx 製になって、ドキュメント品質が非常に良くなったということ。実際に自分でSphinx体験してみれば、

このような、プログラマーがドキュメントを書きたくなってしまうすばらしいツールに乾杯!

は嘘ではないとわかってもらえるとワタシは信じているが、要はPython公式ドキュメントが本当に楽しんで書かれているということなんだろうと思う。

そうなんだけれど、徹頭徹尾全ドキュメントが「パーフェクト」かといえば、やっぱりそうでもないところも稀にあって、例えば class のリンクに飛ばされて行き止まりになるものがあったりする。urllib2 がまさにそれで、これみてどうしろっていうのよ。さらに悪いことにこれ、docstring 付けもされてない。こうなるともう、ソースコードを見るしかない。

CGI の検証をするのに、ブラウザと ajax では制約が多いので、自由で乱暴なクライアントを模擬したくて、urllib2 でやりたかったのだ。さらにいえば、リクエスト・レスポンスの詳細が見たい。ところが、そのデバッグをするためにと Python 2.1 の頃の使用例としてみつけたこれ:

Python2.7では試さないで
 1 >>> import urllib2, httplib
 2 >>> httplib.HTTPConnection.debuglevel = 1
 3 >>> request = urllib2.Request('http://diveintomark.org/xml/atom.xml')
 4 >>> request.add_header('Accept-encoding', 'gzip')
 5 >>> opener = urllib2.build_opener()
 6 >>> f = opener.open(request)
 7 connect: (diveintomark.org, 80)
 8 send: '
 9 GET /xml/atom.xml HTTP/1.0
10 Host: diveintomark.org
11 User-agent: Python-urllib/2.1
12 Accept-encoding: gzip
13 '
14 reply: 'HTTP/1.1 200 OK\r\n'
15 header: Date: Thu, 15 Apr 2004 22:24:39 GMT
16 header: Server: Apache/2.0.49 (Debian GNU/Linux)
17 header: Last-Modified: Thu, 15 Apr 2004 19:45:21 GMT
18 header: ETag: "e842a-3e53-55d97640"
19 header: Accept-Ranges: bytes
20 header: Vary: Accept-Encoding
21 header: Content-Encoding: gzip
22 header: Content-Length: 6289
23 header: Connection: close
24 header: Content-Type: application/atom+xml

これ(httplib.HTTPConnection.debuglevel = 1)は Python 2.7 では有効にならない。

となれば、と、httplib.HTTPConnection.set_debuglevel(1)としたってダメ。これは当たり前。インスタンスメソッドなんだから。

そして前置きした通り、行き止まりドキュメントなうえに、本家サイト該当箇所さえも debug_level について言及されてない。実際はこうなってる:

urllib2.py
1114 class AbstractHTTPHandler(BaseHandler):
1115 
1116     def __init__(self, debuglevel=0):
1117         self._debuglevel = debuglevel
1118 
1119     def set_http_debuglevel(self, level):
1120         self._debuglevel = level

ゆえ、こんなふうに使う:

 1 >>> import urllib2
 2 >>> 
 3 >>> # ----------------------------
 4 ... # setup request
 5 ... cgi = "http://someones.fubar.net/cgi-bin/somewhat.cgi"
 6 >>> 
 7 >>> import json
 8 >>> postdata = json.dumps(
 9 ...     {
10 ...         'nanika': 'atai',
11 ...         'sonota': {
12 ...             'andromeda': 'omae',
13 ...             },
14 ...         },
15 ...     )
16 >>> 
17 >>> req = urllib2.Request(cgi, data=postdata)
18 >>> # ----------------------------
19 ... 
20 >>> handler = urllib2.HTTPHandler(debuglevel=1)
21 >>> opener = urllib2.build_opener(handler)
22 >>> 
23 >>> f = opener.open(req)
24 send: 'POST /cgi-bin/somewhat.cgi HTTP/1.1\r\nAccept-Encoding: identity\r\nContent-Length: 96\r\nHost: someones.fubar.net\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nUser-Agent: Python-urllib/2.7\r\n\r\n{"sonota": {"andromeda": "omae"}, "nanika": "atai"}'
25 reply: 'HTTP/1.1 200 OK\r\n'
26 header: Date: Fri, 03 Jul 2015 20:59:05 GMT
27   ...
28 header: Connection: close
29 >>> 
30 >>> print(f.read())
31   ...

2015-07-06追記
Python 3.x では…。なんの嫌がらせかわからないが、debuglevel は受け取るが使ってくれない。ので役に立たない。

Python 2.7、3.x で同じやり方(*)でやりたいなら、

 1 # mod は Python 2.7 では urllib2、Python 3.x では urllib.request
 2 req = mod.Request(SERVICE_URL, data=postdata)
 3 for k in headers:
 4     req.add_header(k, headers[k])
 5 #print(req.headers)
 6 handler = mod.HTTPHandler()
 7 opener = mod.build_opener(handler)
 8 
 9 f = opener.open(req)
10 #print(f.headers)

のように個々にダンプすればいいと思う。まぁ仕方ないよね。