web-dev-qa-db-fra.com

Demandes Python - Imprimer la demande http entière (brute)?

Lorsque vous utilisez le module requests , existe-t-il un moyen d'imprimer la requête HTTP brute? 

Je ne veux pas que les en-têtes, je veux la ligne de demande, les en-têtes et l'impression du contenu. Est-il possible de voir ce qui est finalement construit à partir d'une requête HTTP?

137
huggie

Depuis v1.2.3 Les requêtes ont ajouté l'objet PreparedRequest. Selon la documentation "il contient les octets exacts qui seront envoyés au serveur".

On peut l'utiliser pour imprimer une requête, comme ceci:

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)

qui produit:

-----------START-----------
POST http://stackoverflow.com/
Content-Length: 7
X-Custom: Test

a=1&b=2

Ensuite, vous pouvez envoyer la demande réelle avec ceci:

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

Ces liens renvoient à la dernière documentation disponible, leur contenu pourrait donc en changer: Avancé - Demandes préparées et API - Classes de niveau inférieur

147
AntonioHerraizS

Note: cette réponse est obsolète. Les versions plus récentes de requests permettent de récupérer le contenu de la demande directement, sous la forme de AntonioHerraizS answer documents.

Il est impossible d'extraire le contenu brut de la requête true de requests, car il ne traite que des objets de niveau supérieur, tels que en-têtes et type de méthode. requests utilise urllib3 pour envoyer des demandes, mais urllib3aussi _ ne traite pas les données brutes - il utilise httplib. Voici une trace de pile représentative d'une demande:

-> 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)

À l'intérieur de la machine httplib, nous pouvons voir que HTTPConnection._send_request utilise indirectement HTTPConnection._send_output, ce qui crée finalement la requête brute et corps (s'il existe), et utilise HTTPConnection.send pour les envoyer séparément. send atteint finalement le socket.

Puisqu'il n'y a pas de crochets pour faire ce que vous voulez, en dernier recours, vous pouvez utiliser le correctif httplib pour récupérer le contenu. C'est une solution fragile, et vous devrez peut-être l'adapter si vous changez httplib. Si vous avez l’intention de distribuer des logiciels à l’aide de cette solution, envisagez d’empaqueter httplib au lieu d’utiliser le système, ce qui est simple, car c’est un module python pur.

Hélas, sans plus tarder, la solution:

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")

qui donne la sortie:

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
38
goncalopp

Une meilleure idée consiste à utiliser la bibliothèque tasks_toolbelt, qui peut générer des requêtes et des réponses sous forme de chaînes à imprimer sur la console. Il gère tous les cas difficiles de fichiers et d’encodages que la solution ci-dessus ne gère pas bien.

C'est aussi simple que ça:

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'))

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

Vous pouvez simplement l'installer en tapant:

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

J'utilise requêtes version 2.18.4 et Python 3

19
Payman

Voici un code qui fait la même chose, mais avec les en-têtes de réponse: 

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()

J'ai passé beaucoup de temps à chercher cela, alors je le laisse ici, si quelqu'un en a besoin.

4
denself

J'utilise la fonction suivante pour formater les requêtes. Cela ressemble à @AntonioHerraizS, sauf que les objets JSON sont également imprimés joliment dans le corps et qu'il étiquette toutes les parties de la demande.

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

Et j'ai une fonction similaire pour formater la réponse:

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
0
Ben