¿Cómo se obtienen los encabezados predeterminados en una solicitud urllib2?
Pregunta
Tengo un cliente web Python que usa urllib2. Es bastante fácil agregar encabezados HTTP a mis solicitudes salientes. Simplemente creo un diccionario de los encabezados que quiero agregar y lo paso al inicializador de Solicitud.
Sin embargo, otro " estándar " Los encabezados HTTP se agregan a la solicitud, así como los personalizados que agrego explícitamente. Cuando huelo la solicitud usando Wireshark, veo encabezados además de los que agrego yo mismo. Mi pregunta es ¿cómo obtengo acceso a estos encabezados? Quiero registrar cada solicitud (incluido el conjunto completo de encabezados HTTP) y no puedo entender cómo.
¿algún puntero?
en pocas palabras: ¿Cómo obtengo todos los encabezados salientes de una solicitud HTTP creada por urllib2?
Solución
Si desea ver la solicitud HTTP literal que se envía y, por lo tanto, ver hasta el último encabezado exactamente como se representa en el cable, puede decirle a urllib2
que use su propia versión de un HTTPHandler
que imprime (o guarda, o lo que sea) la solicitud HTTP saliente.
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/')
El resultado de ejecutar este código es:
GET / HTTP/1.1
Accept-Encoding: identity
Host: www.google.com
Connection: close
User-Agent: Python-urllib/2.6
Otros consejos
La biblioteca urllib2 usa objetos OpenerDirector para manejar la apertura real. Afortunadamente, la biblioteca de Python proporciona valores predeterminados para que no tenga que hacerlo. Sin embargo, son estos objetos OpenerDirector los que están agregando los encabezados adicionales.
Para ver qué son después de que se haya enviado la solicitud (para que pueda iniciar sesión, por ejemplo):
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)
El unredirected_hdrs es donde los OpenerDirectors vuelcan sus encabezados adicionales. Simplemente mirando req.headers
mostrará solo sus propios encabezados: la biblioteca los deja sin molestias.
Si necesita ver los encabezados antes de enviar la solicitud, deberá subclasificar el OpenerDirector para interceptar la transmisión.
Espero que ayude.
EDITAR: olvidé mencionar que, una vez que la solicitud se haya enviado, req.header_items ()
le dará una lista de tuplas de TODOS los encabezados, con los suyos y los añadidos. por el OpenerDirector. Debería haber mencionado esto primero, ya que es el más sencillo :-) Lo siento.
EDIT 2: después de su pregunta sobre un ejemplo para definir su propio controlador, aquí está la muestra que se me ocurrió. La preocupación en cualquier monkeying con la cadena de solicitud es que debemos asegurarnos de que el controlador sea seguro para múltiples solicitudes, por lo que me siento incómodo simplemente reemplazando la definición de putheader en la clase HTTPConnection directamente.
Lamentablemente, debido a que los componentes internos de HTTPConnection y AbstractHTTPHandler son muy internos, tenemos que reproducir gran parte del código de la biblioteca de Python para inyectar nuestro comportamiento personalizado. Suponiendo que no me he burlado a continuación y esto funciona tan bien como lo hizo en mis 5 minutos de prueba, tenga cuidado de volver a visitar este reemplazo si actualiza su versión de Python a un número de revisión (es decir: 2.5.x a 2.5.y o 2.5 a 2.6, etc.).
Por lo tanto, debo mencionar que estoy en Python 2.5.1. Si tiene 2.6 o, particularmente, 3.0, es posible que deba ajustar esto en consecuencia.
Avísame si esto no funciona. Me estoy divirtiendo muchísimo con esta pregunta:
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')]
¿Qué tal algo como esto?
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')
Una solución de bajo nivel:
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
Ejemplo:
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
Otra solución, la bruja usó la idea de ¿Cómo se obtienen los encabezados predeterminados en una solicitud urllib2? Pero no copia el código 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
EDITAR: actualizar la fuente
ver urllib2.py:do_request (línea 1044 (1067)) y urllib2.py:do_open (línea 1073) (línea 293) self.addheaders = [('User-agent', client_version)] (solo 'User-agent' agregado)
Me parece que está buscando los encabezados del objeto de respuesta, que incluyen Connection: close
, etc. Estos encabezados viven en el objeto devuelto por urlopen. Llegar a ellos es bastante fácil:
from urllib2 import urlopen
req = urlopen("http://www.google.com")
print req.headers.headers
req.headers
es una instancia de httplib.HTTPMessage
Debería enviar los encabezados http predeterminados (según lo especificado por w3.org ) junto con los que especifiques. Puede usar una herramienta como WireShark si desea verlos en su totalidad.
Edición :
Si desea registrarlos, puede usar WinPcap para capturar paquetes enviados por aplicaciones específicas ( en tu caso, python). También puede especificar el tipo de paquetes y muchos otros detalles.
-John