urllib2リクエストでデフォルトのヘッダーを取得するにはどうすればよいですか?
質問
urllib2を使用するPython Webクライアントがあります。送信リクエストにHTTPヘッダーを追加するのは簡単です。追加するヘッダーのディクショナリを作成し、それをリクエスト初期化子に渡します。
ただし、その他の「標準」はHTTPヘッダーは、明示的に追加したカスタムヘッダーと同様に、リクエストに追加されます。 Wiresharkを使用してリクエストをスニッフィングすると、自分で追加したヘッダー以外にヘッダーが表示されます。私の質問は、これらのヘッダーにアクセスするにはどうすればよいですか?すべてのリクエスト(HTTPヘッダーの full セットを含む)をログに記録したいのですが、方法がわかりません。
ポインター?
簡単に言うと、urllib2によって作成されたHTTPリクエストからすべての送信ヘッダーを取得するにはどうすればよいですか
解決
送信されるリテラルHTTPリクエストを確認し、したがって、ワイヤ上に表示されているとおりに最後のすべてのヘッダーを確認する場合は、 urllib2
に独自のバージョンを使用するように指示できます発信HTTPリクエストを出力(または保存など)する HTTPHandler
。
import httplib, urllib2
class MyHTTPConnection(httplib.HTTPConnection):
def send(self, s):
print s # or save them, or whatever!
httplib.HTTPConnection.send(self, s)
class MyHTTPHandler(urllib2.HTTPHandler):
def http_open(self, req):
return self.do_open(MyHTTPConnection, req)
opener = urllib2.build_opener(MyHTTPHandler)
response = opener.open('http://www.google.com/')
このコードを実行した結果は次のとおりです。
GET / HTTP/1.1
Accept-Encoding: identity
Host: www.google.com
Connection: close
User-Agent: Python-urllib/2.6
他のヒント
urllib2ライブラリは、OpenerDirectorオブジェクトを使用して実際の開始を処理します。幸いなことに、Pythonライブラリにはデフォルトが用意されているため、必要はありません。ただし、追加のヘッダーを追加しているのは、これらのOpenerDirectorオブジェクトです。
リクエストの送信後にそれらが何であるかを確認するには(たとえば、ログに記録できるように):
req = urllib2.Request(url='http://google.com')
response = urllib2.urlopen(req)
print req.unredirected_hdrs
(produces {'Host': 'google.com', 'User-agent': 'Python-urllib/2.5'} etc)
unredirected_hdrsは、OpenerDirectorsが余分なヘッダーをダンプする場所です。単に req.headers
を見ると、自分のヘッダーのみが表示されます。ライブラリーは、それらを無邪気にしないで残します。
リクエストを送信する前にヘッダーを確認する必要がある場合は、送信を傍受するためにOpenerDirectorをサブクラス化する必要があります。
役立つこと。
編集:リクエストが送信されると、 req.header_items()
はすべてのヘッダーのタプルのリストを提供します。 OpenerDirectorによる。これは最も簡単だから最初に言及すべきだった:-)ごめんなさい。
編集2:独自のハンドラーを定義する例について質問した後、私が思いついたサンプルを示します。リクエストチェーンとの関係における懸念は、ハンドラが複数のリクエストに対して安全であることを確認する必要があることです。そのため、HTTPConnectionクラスのputheaderの定義を直接置き換えるだけでは不快です。
残念ながら、HTTPConnectionとAbstractHTTPHandlerの内部は非常に内部的であるため、カスタム動作を注入するために、Pythonライブラリから多くのコードを再現する必要があります。私が以下のことを馬鹿にしておらず、これが私の5分間のテストと同じように機能すると仮定して、Pythonバージョンをリビジョン番号に更新する場合(つまり、2.5.xから2.5.yまたは2.5から2.6など)。
したがって、Python 2.5.1を使用していることに言及する必要があります。 2.6または特に3.0を使用している場合は、それに応じて調整する必要があります。
これが機能しない場合はお知らせください。私はこの質問を楽しみすぎています:
import urllib2
import httplib
import socket
class CustomHTTPConnection(httplib.HTTPConnection):
def __init__(self, *args, **kwargs):
httplib.HTTPConnection.__init__(self, *args, **kwargs)
self.stored_headers = []
def putheader(self, header, value):
self.stored_headers.append((header, value))
httplib.HTTPConnection.putheader(self, header, value)
class HTTPCaptureHeaderHandler(urllib2.AbstractHTTPHandler):
def http_open(self, req):
return self.do_open(CustomHTTPConnection, req)
http_request = urllib2.AbstractHTTPHandler.do_request_
def do_open(self, http_class, req):
# All code here lifted directly from the python library
host = req.get_host()
if not host:
raise URLError('no host given')
h = http_class(host) # will parse host:port
h.set_debuglevel(self._debuglevel)
headers = dict(req.headers)
headers.update(req.unredirected_hdrs)
headers["Connection"] = "close"
headers = dict(
(name.title(), val) for name, val in headers.items())
try:
h.request(req.get_method(), req.get_selector(), req.data, headers)
r = h.getresponse()
except socket.error, err: # XXX what error?
raise urllib2.URLError(err)
r.recv = r.read
fp = socket._fileobject(r, close=True)
resp = urllib2.addinfourl(fp, r.msg, req.get_full_url())
resp.code = r.status
resp.msg = r.reason
# This is the line we're adding
req.all_sent_headers = h.stored_headers
return resp
my_handler = HTTPCaptureHeaderHandler()
opener = urllib2.OpenerDirector()
opener.add_handler(my_handler)
req = urllib2.Request(url='http://www.google.com')
resp = opener.open(req)
print req.all_sent_headers
shows: [('Accept-Encoding', 'identity'), ('Host', 'www.google.com'), ('Connection', 'close'), ('User-Agent', 'Python-urllib/2.5')]
このようなものはどうですか:
import urllib2
import httplib
old_putheader = httplib.HTTPConnection.putheader
def putheader(self, header, value):
print header, value
old_putheader(self, header, value)
httplib.HTTPConnection.putheader = putheader
urllib2.urlopen('http://www.google.com')
低レベルのソリューション:
import httplib
class HTTPConnection2(httplib.HTTPConnection):
def __init__(self, *args, **kwargs):
httplib.HTTPConnection.__init__(self, *args, **kwargs)
self._request_headers = []
self._request_header = None
def putheader(self, header, value):
self._request_headers.append((header, value))
httplib.HTTPConnection.putheader(self, header, value)
def send(self, s):
self._request_header = s
httplib.HTTPConnection.send(self, s)
def getresponse(self, *args, **kwargs):
response = httplib.HTTPConnection.getresponse(self, *args, **kwargs)
response.request_headers = self._request_headers
response.request_header = self._request_header
return response
例:
conn = HTTPConnection2("www.python.org")
conn.request("GET", "/index.html", headers={
"User-agent": "test",
"Referer": "/",
})
response = conn.getresponse()
response.status、response.reason:
1: 200 OK
response.request_headers:
[('Host', 'www.python.org'), ('Accept-Encoding', 'identity'), ('Referer', '/'), ('User-agent', 'test')]
response.request_header:
GET /index.html HTTP/1.1
Host: www.python.org
Accept-Encoding: identity
Referer: /
User-agent: test
別の解決策、魔女は urllib2リクエストでデフォルトヘッダーを取得するにはどうすればよいですかただし、std-libからコードをコピーしません:
class HTTPConnection2(httplib.HTTPConnection):
"""
Like httplib.HTTPConnection but stores the request headers.
Used in HTTPConnection3(), see below.
"""
def __init__(self, *args, **kwargs):
httplib.HTTPConnection.__init__(self, *args, **kwargs)
self.request_headers = []
self.request_header = ""
def putheader(self, header, value):
self.request_headers.append((header, value))
httplib.HTTPConnection.putheader(self, header, value)
def send(self, s):
self.request_header = s
httplib.HTTPConnection.send(self, s)
class HTTPConnection3(object):
"""
Wrapper around HTTPConnection2
Used in HTTPHandler2(), see below.
"""
def __call__(self, *args, **kwargs):
"""
instance made in urllib2.HTTPHandler.do_open()
"""
self._conn = HTTPConnection2(*args, **kwargs)
self.request_headers = self._conn.request_headers
self.request_header = self._conn.request_header
return self
def __getattribute__(self, name):
"""
Redirect attribute access to the local HTTPConnection() instance.
"""
if name == "_conn":
return object.__getattribute__(self, name)
else:
return getattr(self._conn, name)
class HTTPHandler2(urllib2.HTTPHandler):
"""
A HTTPHandler which stores the request headers.
Used HTTPConnection3, see above.
>>> opener = urllib2.build_opener(HTTPHandler2)
>>> opener.addheaders = [("User-agent", "Python test")]
>>> response = opener.open('http://www.python.org/')
Get the request headers as a list build with HTTPConnection.putheader():
>>> response.request_headers
[('Accept-Encoding', 'identity'), ('Host', 'www.python.org'), ('Connection', 'close'), ('User-Agent', 'Python test')]
>>> response.request_header
'GET / HTTP/1.1\\r\\nAccept-Encoding: identity\\r\\nHost: www.python.org\\r\\nConnection: close\\r\\nUser-Agent: Python test\\r\\n\\r\\n'
"""
def http_open(self, req):
conn_instance = HTTPConnection3()
response = self.do_open(conn_instance, req)
response.request_headers = conn_instance.request_headers
response.request_header = conn_instance.request_header
return response
編集:ソースを更新
urllib2.py:do_request(行1044(1067))およびurllib2.py:do_open(行1073)を参照 (293行目)self.addheaders = [( 'User-agent'、client_version)]( 'User-agent'のみが追加されました)
応答オブジェクトのヘッダー( Connection:close
など)を探しているようです。これらのヘッダーは、urlopenによって返されるオブジェクトに存在します。それらを取得するのは簡単です:
from urllib2 import urlopen
req = urlopen("http://www.google.com")
print req.headers.headers
req.headers
は httplib.HTTPMessage