Solicitudes de Python: ¿imprimir la solicitud de http completa (sin procesar)?

Al utilizar el módulo de requests , ¿hay alguna forma de imprimir la solicitud HTTP sin procesar?

No quiero solo los encabezados, quiero la línea de solicitud, los encabezados y la impresión del contenido. ¿Es posible ver lo que finalmente se construye a partir de una solicitud HTTP?

Desde v1.2.3 Las solicitudes agregaron el objeto PreparedRequest. Según la documentación “contiene los bytes exactos que se enviarán al servidor”.

Uno puede usar esto para imprimir bastante una solicitud, así:

 import requests req = requests.Request('POST','http://stackoverflow.com',headers={'X-Custom':'Test'},data='a=1&b=2') prepared = req.prepare() def pretty_print_POST(req): """ At this point it is completely built and ready to be fired; it is "prepared". However pay attention at the formatting used in this function because it is programmed to be pretty printed and may differ from the actual request. """ print('{}\n{}\n{}\n\n{}'.format( '-----------START-----------', req.method + ' ' + req.url, '\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()), req.body, )) pretty_print_POST(prepared) 

que produce:

 -----------START----------- POST http://stackoverflow.com/ Content-Length: 7 X-Custom: Test a=1&b=2 

Entonces puedes enviar la solicitud real con esto:

 s = requests.Session() s.send(prepared) 

Estos enlaces son a la última documentación disponible, por lo que podrían cambiar en el contenido: Avanzado – Solicitudes preparadas y API – Clases de nivel inferior

Nota: esta respuesta está desactualizada. Las versiones más nuevas de las solicitudes permiten obtener el contenido de la solicitud directamente, como documentos de respuesta de AntonioHerraizS .

No es posible obtener el verdadero contenido sin procesar de la solicitud a partir de las requests , ya que solo se ocupa de objetos de nivel superior, como encabezados y tipo de método . requests utilizan urllib3 para enviar solicitudes, pero urllib3 tampoco se ocupa de los datos en bruto, sino que utiliza httplib . Aquí hay un registro de stack representativo de una solicitud:

 -> r= requests.get("http://google.com") /usr/local/lib/python2.7/dist-packages/requests/api.py(55)get() -> return request('get', url, **kwargs) /usr/local/lib/python2.7/dist-packages/requests/api.py(44)request() -> return session.request(method=method, url=url, **kwargs) /usr/local/lib/python2.7/dist-packages/requests/sessions.py(382)request() -> resp = self.send(prep, **send_kwargs) /usr/local/lib/python2.7/dist-packages/requests/sessions.py(485)send() -> r = adapter.send(request, **kwargs) /usr/local/lib/python2.7/dist-packages/requests/adapters.py(324)send() -> timeout=timeout /usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/connectionpool.py(478)urlopen() -> body=body, headers=headers) /usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/connectionpool.py(285)_make_request() -> conn.request(method, url, **httplib_request_kw) /usr/lib/python2.7/httplib.py(958)request() -> self._send_request(method, url, body, headers) 

Dentro de la maquinaria httplib , podemos ver que HTTPConnection._send_request usa HTTPConnection._send_output , que finalmente crea la solicitud y el cuerpo sin procesar (si existe), y usa HTTPConnection.send para enviarlos por separado. send finalmente llega al zócalo.

Como no hay ganchos para hacer lo que quiere, como último recurso puede hacer un parche httplib para obtener el contenido. Es una solución frágil, y es posible que deba adaptarla si se cambia httplib . Si pretende distribuir software con esta solución, puede considerar empaquetar httplib lugar de usar el sistema, lo cual es fácil, ya que es un módulo de python puro.

Ay, sin más preámbulos, la solución:

 import requests import httplib def patch_send(): old_send= httplib.HTTPConnection.send def new_send( self, data ): print data return old_send(self, data) #return is not necessary, but never hurts, in case the library is changed httplib.HTTPConnection.send= new_send patch_send() requests.get("http://www.python.org") 

que produce la salida:

 GET / HTTP/1.1 Host: www.python.org Accept-Encoding: gzip, deflate, compress Accept: */* User-Agent: python-requests/2.1.0 CPython/2.7.3 Linux/3.2.0-23-generic-pae 

Una idea aún mejor es utilizar la biblioteca request_toolbelt, que puede volcar tanto las solicitudes como las respuestas como cadenas para que pueda imprimirlas en la consola. Maneja todos los casos difíciles con archivos y codificaciones que la solución anterior no maneja bien.

Es tan fácil como esto:

 import requests from requests_toolbelt.utils import dump resp = requests.get('https://httpbin.org/redirect/5') data = dump.dump_all(resp) print(data.decode('utf-8')) 

Fuente: https://toolbelt.readthedocs.org/en/latest/dumputils.html

Simplemente puede instalarlo escribiendo:

 pip install requests_toolbelt 
 import requests response = requests.post('http://httpbin.org/post', data={'key1':'value1'}) print(response.request.body) print(response.request.headers) 

Estoy usando solicitudes versión 2.18.4 y Python 3

Aquí hay un código que hace lo mismo, pero con encabezados de respuesta:

 import socket def patch_requests(): old_readline = socket._fileobject.readline if not hasattr(old_readline, 'patched'): def new_readline(self, size=-1): res = old_readline(self, size) print res, return res new_readline.patched = True socket._fileobject.readline = new_readline patch_requests() 

Pasé mucho tiempo buscando esto, así que lo dejo aquí, si alguien lo necesita.

Utilizo la siguiente función para dar formato a las solicitudes. Es como @AntonioHerraizS, excepto que también imprimirá los objetos JSON en el cuerpo y etiquetará todas las partes de la solicitud.

 format_json = functools.partial(json.dumps, indent=2, sort_keys=True) indent = functools.partial(textwrap.indent, prefix=' ') def format_prepared_request(req): """Pretty-format 'requests.PreparedRequest' Example: res = requests.post(...) print(format_prepared_request(res.request)) req = requests.Request(...) req = req.prepare() print(format_prepared_request(res.request)) """ headers = '\n'.join(f'{k}: {v}' for k, v in req.headers.items()) content_type = req.headers.get('Content-Type', '') if 'application/json' in content_type: try: body = format_json(json.loads(req.body)) except json.JSONDecodeError: body = req.body else: body = req.body s = textwrap.dedent(""" REQUEST ======= endpoint: {method} {url} headers: {headers} body: {body} ======= """).strip() s = s.format( method=req.method, url=req.url, headers=indent(headers), body=indent(body), ) return s 

Y tengo una función similar para formatear la respuesta:

 def format_response(resp): """Pretty-format 'requests.Response'""" headers = '\n'.join(f'{k}: {v}' for k, v in resp.headers.items()) content_type = resp.headers.get('Content-Type', '') if 'application/json' in content_type: try: body = format_json(resp.json()) except json.JSONDecodeError: body = resp.text else: body = resp.text s = textwrap.dedent(""" RESPONSE ======== status_code: {status_code} headers: {headers} body: {body} ======== """).strip() s = s.format( status_code=resp.status_code, headers=indent(headers), body=indent(body), ) return s