Permintaan python - cetak seluruh permintaan http (mentah)?

197

Saat menggunakan requestsmodul , apakah ada cara untuk mencetak permintaan HTTP mentah?

Saya tidak ingin hanya header, saya ingin baris permintaan, header, dan cetakan konten. Apakah mungkin untuk melihat apa yang akhirnya dibangun dari permintaan HTTP?

Huggie
sumber
9
@RickyA dia bertanya tentang isi permintaan, bukan tanggapan
goncalopp
2
Itu pertanyaan yang bagus. Dari melihat sumbernya, sepertinya tidak ada cara untuk mendapatkan konten mentah dari permintaan yang disiapkan, dan itu hanya berseri ketika dikirim. Sepertinya itu akan menjadi fitur yang bagus.
Tim Pierce
Nah, Anda juga bisa memulai wireshark dan melihatnya seperti itu.
RickyA
@ qwrty akan sulit untuk mengintegrasikan ini sebagai requestsfitur, karena itu berarti penulisan ulang / bypass urllib3dan httplib. Lihat jejak tumpukan di bawah ini
goncalopp
Ini bekerja untuk saya - stackoverflow.com/questions/10588644/…
Ajay

Jawaban:

213

Sejak v1.2.3 Permintaan menambahkan objek PreparedRequest. Sesuai dokumentasi "itu berisi byte yang tepat yang akan dikirim ke server".

Orang dapat menggunakan ini untuk mencetak permintaan, seperti:

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)

yang menghasilkan:

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

a=1&b=2

Kemudian Anda dapat mengirim permintaan aktual dengan ini:

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

Tautan-tautan ini menuju ke dokumentasi terbaru yang tersedia, sehingga dapat berubah konten: Lanjutan - Permintaan yang disiapkan dan API - Kelas tingkat bawah

AntonioHerraizS
sumber
2
Ini jauh lebih kuat daripada metode patching monyet saya. Memutakhirkan requestsitu mudah, jadi saya pikir ini harus menjadi jawaban yang diterima
goncalopp
69
Jika Anda menggunakan sederhana response = requests.post(...)(atau requests.getatau requests.put, dll) metode, Anda benar-benar bisa mendapatkan PreparedResponsemelalui response.request. Ini dapat menyimpan pekerjaan memanipulasi secara manual requests.Requestdan requests.Session, jika Anda tidak perlu mengakses data http mentah sebelum Anda menerima tanggapan.
Gershom
2
Jawaban yang bagus. Satu hal yang Anda mungkin ingin perbarui adalah bahwa jeda baris dalam HTTP seharusnya \ r \ n bukan hanya \ n.
ltc
3
bagaimana dengan bagian versi protokol HTTP setelah url? seperti 'HTTP / 1.1'? yang tidak ditemukan saat mencetak menggunakan printer cantik Anda.
Sajuuk
1
Diperbarui untuk menggunakan CRLF, karena itulah yang dibutuhkan RFC 2616, dan itu bisa menjadi masalah bagi parser yang sangat ketat
nimish
55
import requests
response = requests.post('http://httpbin.org/post', data={'key1':'value1'})
print(response.request.body)
print(response.request.headers)

Saya menggunakan permintaan versi 2.18.4 dan Python 3

Payman
sumber
44

Catatan: jawaban ini sudah usang. Versi requests dukungan yang lebih baru mendapatkan konten permintaan secara langsung, seperti dokumen jawaban AntonioHerraizS .

Tidak mungkin mendapatkan konten mentah yang benar dari permintaan requests, karena hanya berurusan dengan objek tingkat yang lebih tinggi, seperti header dan tipe metode . requestsdigunakan urllib3untuk mengirim permintaan, tetapi urllib3 juga tidak berurusan dengan data mentah - yang digunakan httplib. Berikut ini jejak tumpukan representatif dari suatu permintaan:

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

Di dalam httplibmesin, kita dapat melihat HTTPConnection._send_requestpenggunaan tidak langsung HTTPConnection._send_output, yang akhirnya membuat permintaan dan isi mentah (jika ada), dan menggunakan HTTPConnection.senduntuk mengirimkannya secara terpisah. sendakhirnya mencapai soket.

Karena tidak ada kaitan untuk melakukan apa yang Anda inginkan, sebagai upaya terakhir Anda dapat menambal monyet httplibuntuk mendapatkan konten. Ini adalah solusi yang rapuh, dan Anda mungkin perlu menyesuaikannya jika httplibdiubah. Jika Anda bermaksud untuk mendistribusikan perangkat lunak menggunakan solusi ini, Anda mungkin ingin mempertimbangkan kemasan httplibdaripada menggunakan sistem, yang mudah, karena ini adalah modul python murni.

Sayangnya, tanpa basa-basi, solusinya:

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

yang menghasilkan 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
goncalopp
sumber
Hai goncalopp, jika saya memanggil prosedur patch_send () kali ke-2 (setelah permintaan ke-2), maka ia mencetak data dua kali (jadi 2x kali output seperti yang Anda tunjukkan di atas)? Jadi, jika saya akan melakukan permintaan ke-3, itu akan mencetaknya 3x kali dan seterusnya ... Adakah yang tahu bagaimana mendapatkan hanya output satu kali? Terima kasih sebelumnya.
opstalj
@opstalj Anda tidak boleh menelepon patch_sendberkali-kali, hanya sekali, setelah mengimporhttplib
goncalopp
40

Gagasan yang bahkan lebih baik adalah dengan menggunakan perpustakaan request_toolbelt, yang dapat membuang permintaan dan tanggapan sebagai string bagi Anda untuk mencetak ke konsol. Ini menangani semua kasus rumit dengan file dan pengkodean yang tidak ditangani dengan baik oleh solusi di atas.

Semudah ini:

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

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

Anda cukup menginstalnya dengan mengetik:

pip install requests_toolbelt
Emil Stenström
sumber
2
Ini sepertinya tidak membuang permintaan tanpa mengirimnya.
Dobes Vandermeer
1
dump_all tampaknya tidak berfungsi sebagaimana mestinya ketika saya mendapatkan "TypeError: tidak dapat menggabungkan objek 'str' dan 'UUID'" dari panggilan.
rtaft
@rtaft: Silakan laporkan ini sebagai bug di repositori github mereka: github.com/sigmavirus24/requests-toolbelt/…
Emil Stenström
Itu mencetak dump dengan> dan <tanda, apakah mereka bagian dari permintaan yang sebenarnya?
Jay
1
@Jay Sepertinya mereka tergantung pada permintaan / respons aktual untuk penampilan ( github.com/requests/toolbelt/blob/master/requests_toolbelt/… ) dan dapat ditentukan dengan mengirimkan request_prefix = b '{some_request_prefix}', response_prefix = ', response_prefix =' b '{some_response_prefix}' to dump_all ( github.com/requests/toolbelt/blob/master/requests_toolbelt/… )
Christian Reall-Fluharty
7

Berikut adalah kode, yang membuatnya sama, tetapi dengan header respons:

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

Saya menghabiskan banyak waktu mencari ini, jadi saya meninggalkannya di sini, jika seseorang membutuhkan.

padat
sumber
4

Saya menggunakan fungsi berikut untuk memformat permintaan. Ini seperti @AntonioHerraizS kecuali itu akan cukup mencetak objek JSON di dalam tubuh juga, dan label semua bagian dari permintaan.

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

Dan saya memiliki fungsi serupa untuk memformat respons:

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

requestsmendukung yang disebut kait acara (pada 2.23 sebenarnya hanya ada responsekait). Pengait dapat digunakan berdasarkan permintaan untuk mencetak data pasangan permintaan-respons penuh, termasuk URL, tajuk, dan badan yang efektif, seperti:

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

Menjalankannya mencetak:

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

Anda mungkin ingin mengubah res.textke res.contentjika responsnya adalah biner.

saaj
sumber