Richieste Python: stampa l'intera richiesta http (non elaborata)?


198

Durante l'utilizzo del requestsmodulo , esiste un modo per stampare la richiesta HTTP non elaborata?

Non voglio solo le intestazioni, voglio la riga della richiesta, le intestazioni e la stampa dei contenuti. È possibile vedere ciò che alla fine è costruito dalla richiesta HTTP?


2
Questa è una buona domanda. Guardando la fonte, non sembra che ci sia alcun modo per ottenere il contenuto non elaborato di una richiesta preparata ed è serializzato solo quando viene inviato. Sembra che sarebbe una buona caratteristica.
Tim Pierce,

Bene, potresti anche avviare WireShark e vederlo in quel modo.
Ricky,

@qwrrty sarebbe difficile integrarlo come requestsfunzionalità, poiché significherebbe riscrivere / bypassare urllib3e httplib. Vedi la traccia dello stack di seguito
goncalopp

Questo ha funzionato per me - stackoverflow.com/questions/10588644/…
Ajay

Risposte:


217

Poiché le richieste v1.2.3 hanno aggiunto l'oggetto PreparedRequest. Secondo la documentazione "contiene i byte esatti che verranno inviati al server".

Si può usare questo per stampare piuttosto una richiesta, in questo modo:

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{}\r\n{}\r\n\r\n{}'.format(
        '-----------START-----------',
        req.method + ' ' + req.url,
        '\r\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()),
        req.body,
    ))

pretty_print_POST(prepared)

che produce:

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

a=1&b=2

Quindi è possibile inviare la richiesta effettiva con questo:

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

Questi collegamenti sono all'ultima documentazione disponibile, quindi potrebbero cambiare nel contenuto: Avanzate - Richieste preparate e API - Classi di livello inferiore


2
Questo è molto più robusto del mio metodo di patching delle scimmie. L'aggiornamento requestsè semplice, quindi penso che questa dovrebbe diventare la risposta accettata
goncalopp,

71
Se si utilizza il semplice response = requests.post(...)(o requests.geto requests.putmetodi, ecc), si può effettivamente ottenere il PreparedResponsetramite response.request. Può salvare il lavoro di manipolazione manuale requests.Requeste requests.Session, se non è necessario accedere ai dati http grezzi prima di ricevere una risposta.
Gershom,

2
Buona risposta. Una cosa che potresti voler aggiornare è che le interruzioni di riga in HTTP dovrebbero essere \ r \ n non solo \ n.
ltc,

3
che dire della parte della versione del protocollo HTTP subito dopo l'URL? come "HTTP / 1.1"? che non si trova quando si stampa usando la tua bella stampante.
Sajuuk,

1
Aggiornato per utilizzare CRLF, poiché è quello che richiede RFC 2616 e potrebbe essere un problema per parser molto severi
nimish

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

Sto usando le richieste versione 2.18.4 e Python 3


44

Nota: questa risposta è obsoleta. Versioni più recenti di requests supporto ricevono direttamente il contenuto della richiesta, come i documenti di risposta di AntonioHerraizS .

Non è possibile estrarre il vero contenuto non elaborato della richiesta requests, poiché si occupa solo di oggetti di livello superiore, come intestazioni e tipo di metodo . requestsusi urllib3di inviare richieste, ma urllib3 anche non tratta dati grezzi - usa httplib. Ecco una traccia dello stack rappresentativa di una richiesta:

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

All'interno del httplibmacchinario, possiamo vedere gli HTTPConnection._send_requestusi indiretti HTTPConnection._send_output, che alla fine creano la richiesta e il corpo grezzi (se esistenti) e li usano HTTPConnection.sendper inviarli separatamente. sendraggiunge finalmente la presa.

Dal momento che non ci sono ganci per fare quello che vuoi, come ultima risorsa puoi scimmiettare patch httplibper ottenere il contenuto. È una soluzione fragile e potrebbe essere necessario adattarla se httplibviene modificata. Se hai intenzione di distribuire software usando questa soluzione, potresti prendere in considerazione la creazione httplibdi pacchetti invece di usare il sistema, il che è facile, dal momento che è un modulo Python puro.

Purtroppo, senza ulteriori indugi, la soluzione:

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

che produce l'output:

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

Ciao goncalopp, se chiamo la procedura patch_send () una seconda volta (dopo una seconda richiesta), quindi stampa i dati due volte (quindi 2x volte l'output come hai mostrato sopra)? Quindi, se dovessi fare una terza richiesta, la stampa 3 volte e così via ... Qualche idea su come ottenere una sola uscita? Grazie in anticipo.
opstalj,

@opstalj non dovresti chiamare patch_sendpiù volte, una sola volta, dopo l'importazionehttplib
goncalopp

40

Un'idea ancora migliore è usare la libreria request_toolbelt, che può scaricare sia le richieste che le risposte come stringhe per la stampa sulla console. Gestisce tutti i casi difficili con file e codifiche che la soluzione di cui sopra non gestisce bene.

È facile come questo:

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

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

Puoi semplicemente installarlo digitando:

pip install requests_toolbelt

2
Tuttavia, ciò non sembra scaricare la richiesta senza inviarla.
Dobes Vandermeer

1
dump_all non sembra funzionare correttamente poiché ottengo "TypeError: impossibile concatenare gli oggetti 'str' e 'UUID'" dalla chiamata.
ritorno il

@rtaft: segnalalo come un bug nel loro repository github: github.com/sigmavirus24/requests-toolbelt/…
Emil Stenström,

Stampa il dump con i segni> e <, fanno parte della richiesta effettiva?
Jay,

1
@Jay Sembra che siano anteposti alla richiesta / risposta effettiva per l'apparenza ( github.com/requests/toolbelt/blob/master/requests_toolbelt/… ) e possono essere specificati passando request_prefix = b '{some_request_prefix}', response_prefix = b '{some_response_prefix}' a dump_all ( github.com/requests/toolbelt/blob/master/requests_toolbelt/… )
Christian Reall-Fluharty

7

Ecco un codice, che rende lo stesso, ma con le intestazioni di risposta:

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

Ho trascorso molto tempo a cercarlo, quindi lo lascio qui, se qualcuno ha bisogno.


3

Uso la seguente funzione per formattare le richieste. È come @AntonioHerraizS, tranne per il fatto che stamperà anche oggetti JSON nel corpo ed etichetterà tutte le parti della richiesta.

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

E ho una funzione simile per formattare la risposta:

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

1

requestssupporta i cosiddetti hook di eventi (a partire dalla 2.23 in realtà c'è solo responsehook). L'hook può essere utilizzato su una richiesta per stampare i dati completi della coppia richiesta-risposta, inclusi URL, intestazioni e corpi efficaci, come:

import textwrap
import requests

def print_roundtrip(response, *args, **kwargs):
    format_headers = lambda d: '\n'.join(f'{k}: {v}' for k, v in d.items())
    print(textwrap.dedent('''
        ---------------- request ----------------
        {req.method} {req.url}
        {reqhdrs}

        {req.body}
        ---------------- response ----------------
        {res.status_code} {res.reason} {res.url}
        {reshdrs}

        {res.text}
    ''').format(
        req=response.request, 
        res=response, 
        reqhdrs=format_headers(response.request.headers), 
        reshdrs=format_headers(response.headers), 
    ))

requests.get('https://httpbin.org/', hooks={'response': print_roundtrip})

Eseguendolo stampa:

---------------- request ----------------
GET https://httpbin.org/
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive

None
---------------- response ----------------
200 OK https://httpbin.org/
Date: Thu, 14 May 2020 17:16:13 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 9593
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

<!DOCTYPE html>
<html lang="en">
...
</html>

Si consiglia di cambiare res.textper res.contentse la risposta è binario.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.