Python-Anfragen - gesamte http-Anfrage drucken (roh)?

197

Gibt es während der Verwendung des requestsModuls eine Möglichkeit, die unformatierte HTTP-Anforderung zu drucken?

Ich möchte nicht nur die Überschriften, sondern auch die Anforderungszeile, die Überschriften und den Ausdruck des Inhalts. Ist es möglich zu sehen, was letztendlich aus einer HTTP-Anfrage aufgebaut ist?

Huggie
quelle
9
@ RickyA er fragt nach dem Inhalt der Anfrage, nicht nach der Antwort
Goncalopp
2
Das ist eine gute Frage. Wenn man sich die Quelle ansieht, scheint es keine Möglichkeit zu geben, den Rohinhalt einer vorbereiteten Anfrage zu erhalten, und sie wird nur serialisiert, wenn sie gesendet wird. Das scheint eine gute Funktion zu sein.
Tim Pierce
Nun, Sie könnten auch Wireshark starten und es so sehen.
RickyA
@qwrrty es wäre schwierig, dies als requestsFeature zu integrieren , da es bedeuten würde, neu zu schreiben / zu umgehen urllib3und httplib. Siehe die Stapelverfolgung unten
Goncalopp
Dies funktionierte für mich - stackoverflow.com/questions/10588644/…
Ajay

Antworten:

213

Seit v1.2.3 Requests wurde das PreparedRequest-Objekt hinzugefügt. Gemäß der Dokumentation "enthält es die genauen Bytes, die an den Server gesendet werden".

Man kann dies verwenden, um eine Anfrage hübsch auszudrucken, wie folgt:

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)

welches produziert:

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

a=1&b=2

Dann können Sie die eigentliche Anfrage damit senden:

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

Diese Links verweisen auf die neueste verfügbare Dokumentation, sodass sich der Inhalt ändern kann: Erweitert - Vorbereitete Anforderungen und API - Klassen niedrigerer Ebene

AntonioHerraizS
quelle
2
Dies ist viel robuster als meine Affen-Patching-Methode. Das Upgrade requestsist unkompliziert, daher denke ich, dass dies die akzeptierte Antwort sein sollte
Goncalopp
68
Wenn Sie die einfache Verwendung response = requests.post(...)(oder requests.getoder requests.putusw.) Methoden, können Sie tatsächlich das bekommen PreparedResponsedurch response.request. Dies erspart Ihnen die manuelle Bearbeitung requests.Requestund requests.Sessionwenn Sie nicht auf die http-Rohdaten zugreifen müssen, bevor Sie eine Antwort erhalten.
Gershom
2
Gute Antwort. Eine Sache, die Sie möglicherweise aktualisieren möchten, ist, dass die Zeilenumbrüche in HTTP \ r \ n und nicht nur \ n sein sollten.
ltc
3
Was ist mit dem HTTP-Protokollversionsteil direkt nach der URL? wie 'HTTP / 1.1'? Dies wird beim Ausdrucken mit Ihrem hübschen Drucker nicht gefunden.
Sajuuk
1
Aktualisiert, um CRLF zu verwenden, da dies für RFC 2616 erforderlich ist und es ein Problem für sehr strenge Parser sein könnte
nimish
55
import requests
response = requests.post('http://httpbin.org/post', data={'key1':'value1'})
print(response.request.body)
print(response.request.headers)

Ich verwende Anfragen Version 2.18.4 und Python 3

Payman
quelle
44

Hinweis: Diese Antwort ist veraltet. Neuere Versionen von requests Unterstützung der Anforderung von Inhalten direkt bekommen, als AntonioHerraizS Antwort Dokumente .

Es ist nicht möglich, den wahren Rohinhalt der Anforderung zu ermitteln requests, da nur Objekte höherer Ebene wie Header und Methodentyp behandelt werden . requestswird urllib3zum Senden von Anfragen verwendet, behandelt aber urllib3 auch keine Rohdaten - es wird verwendet httplib. Hier ist eine repräsentative Stapelverfolgung einer Anforderung:

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

Innerhalb der httplibMaschinerie können wir HTTPConnection._send_requestindirekte Verwendungen sehen HTTPConnection._send_output, die schließlich die Rohanforderung und den Hauptkörper (falls vorhanden) erstellen und verwenden HTTPConnection.send, um sie separat zu senden. senderreicht endlich die Steckdose.

Da es keine Haken gibt, um das zu tun, was Sie wollen, können Sie als letzten Ausweg Affen-Patches erstellen httplib, um den Inhalt zu erhalten. Es ist eine fragile Lösung, und Sie müssen sie möglicherweise anpassen, wenn sie httplibgeändert wird. Wenn Sie beabsichtigen, Software mit dieser Lösung zu vertreiben, sollten Sie das Packen httplibanstelle des Systems in Betracht ziehen , was einfach ist, da es sich um ein reines Python-Modul handelt.

Leider ohne weiteres die Lösung:

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

was die Ausgabe ergibt:

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
Goncalopp
quelle
Hallo Goncalopp, wenn ich die Prozedur patch_send () ein zweites Mal (nach einer zweiten Anforderung) aufrufe, werden die Daten zweimal gedruckt (also das Zweifache der Ausgabe, wie Sie oben gezeigt haben)? Wenn ich also eine dritte Anfrage machen würde, würde sie dreimal gedruckt und so weiter ... Irgendeine Idee, wie man die Ausgabe nur einmal erhält? Danke im Voraus.
opstalj
@opstalj Sie sollten nicht patch_sendmehrmals anrufen , nur einmal, nach dem Importhttplib
Goncalopp
40

Eine noch bessere Idee ist die Verwendung der Bibliothek "request_toolbelt", mit der sowohl Anforderungen als auch Antworten als Zeichenfolgen ausgegeben werden können, die Sie auf der Konsole drucken können. Es behandelt alle schwierigen Fälle mit Dateien und Codierungen, die mit der obigen Lösung nicht gut behandelt werden.

So einfach ist das:

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

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

Sie können es einfach installieren, indem Sie Folgendes eingeben:

pip install requests_toolbelt
Emil Stenström
quelle
2
Dies scheint die Anfrage jedoch nicht zu sichern, ohne sie zu senden.
Dobes Vandermeer
1
dump_all scheint nicht richtig zu funktionieren, da der Aufruf "TypeError: Objekte 'str' und 'UUID' kann nicht verkettet werden".
Rtaft
@rtaft: Bitte melden Sie dies als Fehler in ihrem Github-Repository: github.com/sigmavirus24/requests-toolbelt/…
Emil Stenström
Der Dump wird mit den Zeichen> und <gedruckt. Sind sie Teil der tatsächlichen Anforderung?
Jay
1
@Jay Es sieht so aus, als ob sie der tatsächlichen Anfrage / Antwort für das Erscheinen vorangestellt sind ( github.com/requests/toolbelt/blob/master/requests_toolbelt/… ) und durch Übergabe von request_prefix = b '{some_request_prefix}', response_prefix = angegeben werden können b '{some_response_prefix}' to dump_all ( github.com/requests/toolbelt/blob/master/requests_toolbelt/… )
Christian Reall-Fluharty
7

Hier ist ein Code, der dasselbe macht, aber mit Antwortheadern:

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

Ich habe viel Zeit damit verbracht, danach zu suchen, also lasse ich es hier, wenn jemand es braucht.

sich selbst
quelle
4

Ich benutze die folgende Funktion, um Anfragen zu formatieren. Es ist wie bei @AntonioHerraizS, nur dass JSON-Objekte im Textkörper hübsch gedruckt werden und alle Teile der Anforderung beschriftet werden.

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

Und ich habe eine ähnliche Funktion, um die Antwort zu formatieren:

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
Ben
quelle
1

requestsunterstützt sogenannte Event-Hooks (ab 2.23 gibt es eigentlich nur noch Hooksresponse ). Der Hook kann für eine Anforderung verwendet werden, um die vollständigen Daten des Anforderungs-Antwort-Paares zu drucken, einschließlich der effektiven URL, Header und Textkörper, wie z.

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

Wenn Sie es ausführen, wird Folgendes gedruckt:

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

Möglicherweise möchten Sie zu ändern res.text, res.contentwenn die Antwort binär ist.

saaj
quelle