Comment obtenir les en-têtes par défaut dans une requête urllib2?
Question
J'ai un client Web Python qui utilise urllib2. Il est assez facile d'ajouter des en-têtes HTTP à mes requêtes sortantes. Je viens de créer un dictionnaire des en-têtes que je veux ajouter et je le passe à l'initialiseur de demande.
Cependant, d’autres "standards" " Les en-têtes HTTP sont ajoutés à la demande ainsi que ceux personnalisés que j’ai explicitement ajoutés. Lorsque je renifle la demande à l'aide de Wireshark, je vois des en-têtes en plus de ceux que j'ai ajoutés moi-même. Ma question est la suivante: comment puis-je accéder à ces en-têtes? Je souhaite consigner toutes les demandes (y compris l'ensemble d'en-têtes HTTP complets ), mais je ne vois pas comment.
des pointeurs?
En résumé: comment puis-je obtenir tous les en-têtes sortants d’une requête HTTP créée par urllib2?
La solution
Si vous voulez voir la requête HTTP littérale qui est envoyée, et donc voir chaque dernier en-tête exactement tel qu'il est représenté sur le câble, vous pouvez indiquer à urllib2
d'utiliser votre propre version de un HTTPHandler
qui imprime (ou enregistre, ou autre) la requête HTTP sortante.
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/')
Le résultat de l'exécution de ce code est:
GET / HTTP/1.1
Accept-Encoding: identity
Host: www.google.com
Connection: close
User-Agent: Python-urllib/2.6
Autres conseils
La bibliothèque urllib2 utilise des objets OpenerDirector pour gérer l’ouverture réelle. Heureusement, la bibliothèque python fournit des valeurs par défaut afin que vous n'ayez pas à le faire. Cependant, ce sont ces objets OpenerDirector qui ajoutent les en-têtes supplémentaires.
Pour voir ce qu'ils sont après l'envoi de la demande (pour pouvoir la consigner par exemple):
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)
Le unredirected_hdrs est l'endroit où les OpenerDirectors vident leurs en-têtes supplémentaires. Il suffit de regarder req.headers
pour afficher uniquement vos propres en-têtes - la bibliothèque vous en laisse les droits.
Si vous avez besoin de voir les en-têtes avant d'envoyer la demande, vous devez sous-classer OpenerDirector afin d'intercepter la transmission.
L’espoir que cela aide.
EDIT: j'ai oublié de mentionner qu'une fois la demande envoyée, req.header_items ()
vous donnera une liste de tuples de TOUS les en-têtes, avec les vôtres et ceux ajoutés. par le OpenerDirector. J'aurais dû le mentionner en premier car c'est le plus simple :-) Désolé.
EDIT 2: Après votre question sur un exemple de définition de votre propre gestionnaire, voici l’exemple que j’ai proposé. La préoccupation de tout singe avec la chaîne de demandes est que nous devons nous assurer que le gestionnaire est sûr pour plusieurs demandes, c'est pourquoi je ne suis pas à l'aise de simplement remplacer la définition de putheader sur la classe HTTPConnection.
Malheureusement, les composants internes de HTTPConnection et de AbstractHTTPHandler étant très internes, nous devons reproduire une grande partie du code de la bibliothèque python pour injecter notre comportement personnalisé. En supposant que je n'ai pas fait de gaffe ci-dessous et que cela fonctionne aussi bien que dans mes 5 minutes de test, veillez à revoir cette substitution si vous mettez à jour votre version de Python avec un numéro de révision (c'est-à-dire: 2.5.x à 2.5.y ou 2,5 à 2,6, etc.).
Je dois donc mentionner que je suis sur Python 2.5.1. Si vous avez 2.6 ou, en particulier, 3.0, vous devrez peut-être ajuster cela en conséquence.
S'il vous plaît laissez-moi savoir si cela ne fonctionne pas. Je me fais trop plaisir avec cette question:
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')]
Que diriez-vous de quelque chose comme ça:
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')
Une solution de bas niveau:
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
Exemple:
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
Une autre solution, qui utilise l'idée de Comment obtenir les en-têtes par défaut dans une requête urllib2? Mais ne copie pas le code à partir de 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
EDIT: met à jour la source
voir urllib2.py:do_request (ligne 1044 (1067)) et urllib2.py:do_open (ligne 1073) (ligne 293) self.addheaders = [('User-agent', version_client)] (uniquement 'User-agent' ajouté)
Il me semble que vous cherchez les en-têtes de l'objet de réponse, qui incluent Connexion: fermer
, etc. Ces en-têtes résident dans l'objet renvoyé par urlopen. Les atteindre est assez facile:
from urllib2 import urlopen
req = urlopen("http://www.google.com")
print req.headers.headers
req.headers
est une instance de httplib.HTTPMessage
Il devrait envoyer les en-têtes http par défaut (comme spécifié par w3.org ) à côté de ceux que vous spécifiez. Vous pouvez utiliser un outil tel que WireShark pour les afficher intégralement.
Modifier:
Si vous souhaitez les consigner, vous pouvez utiliser WinPcap pour capturer les paquets envoyés par des applications spécifiques ( dans votre cas, python). Vous pouvez également spécifier le type de paquets et de nombreux autres détails.
-John