คำขอ Python - พิมพ์คำขอ http ทั้งหมด (raw) หรือไม่


197

ในขณะที่ใช้requestsโมดูลมีวิธีใดในการพิมพ์คำร้องขอ HTTP HTTP หรือไม่

ฉันไม่ต้องการแค่ส่วนหัวฉันต้องการบรรทัดคำขอส่วนหัวและเนื้อหาที่พิมพ์ออกมา เป็นไปได้ไหมที่จะเห็นว่าท้ายที่สุดสิ่งที่สร้างขึ้นจากคำขอ HTTP


9
@RickyA เขาถามเกี่ยวกับเนื้อหาของคำขอไม่ใช่คำตอบ
goncalopp

2
นั่นเป็นคำถามที่ดี จากการดูที่แหล่งข้อมูลดูเหมือนว่าจะไม่มีวิธีใดที่จะได้รับเนื้อหาดิบของคำขอที่เตรียมไว้และจะถูกทำให้เป็นอนุกรมเมื่อมีการส่งเท่านั้น ดูเหมือนว่ามันจะเป็นคุณสมบัติที่ดี
Tim Pierce

คุณสามารถเริ่มต้น wireshark และเห็นมันเป็นอย่างนั้น
RickyA

@qwrrty มันจะเป็นเรื่องยากที่จะบูรณาการนี้เป็นrequestsคุณลักษณะตามที่มันจะหมายถึง rewritting / อ้อมและurllib3 httplibดูสแต็กติดตามด้านล่าง
goncalopp

สิ่งนี้ใช้ได้กับฉัน - stackoverflow.com/questions/10588644/…
Ajay

คำตอบ:


215

เนื่องจาก v1.2.3คำขอเพิ่มวัตถุ PreparedRequest ตามเอกสาร "มันมีจำนวนไบต์ที่แน่นอนที่จะถูกส่งไปยังเซิร์ฟเวอร์"

หนึ่งสามารถใช้สิ่งนี้เพื่อพิมพ์คำขอสวยเช่น:

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)

ซึ่งผลิต:

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

a=1&b=2

จากนั้นคุณสามารถส่งคำขอจริงด้วยสิ่งนี้:

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

ลิงก์เหล่านี้เป็นเอกสารล่าสุดที่มีอยู่ดังนั้นพวกเขาอาจเปลี่ยนแปลงเนื้อหา: ขั้นสูง - คำขอที่เตรียมไว้และAPI - ชั้นเรียนระดับล่าง


2
มันมีประสิทธิภาพมากกว่าวิธีการปะแก้ลิงของฉัน การอัปเกรดrequestsเป็นเรื่องตรงไปตรงมาดังนั้นฉันคิดว่านี่ควรเป็นคำตอบที่ยอมรับ
goncalopp

70
หากคุณใช้วิธีการแบบง่าย ๆresponse = requests.post(...)(หรือrequests.getหรือrequests.putเป็นต้น) คุณสามารถPreparedResponseผ่านresponse.requestไปได้ มันสามารถบันทึกงานการจัดการด้วยตนเองrequests.Requestและrequests.Sessionถ้าคุณไม่จำเป็นต้องเข้าถึงข้อมูล http ดิบก่อนที่คุณจะได้รับการตอบกลับ
Gershom

2
คำตอบที่ดี. สิ่งหนึ่งที่คุณอาจต้องการอัปเดตคือการแบ่งบรรทัดใน HTTP ควรเป็น \ r \ n ไม่ใช่แค่ \ n
ltc

3
รุ่น HTTP โปรโตคอลเกี่ยวกับอะไรส่วนหลังจาก url? เช่น 'HTTP / 1.1' ที่ไม่พบเมื่อพิมพ์โดยใช้เครื่องพิมพ์สวยของคุณ
Sajuuk

1
อัปเดตเพื่อใช้ CRLF เนื่องจากเป็นสิ่งที่ RFC 2616 ต้องการและอาจเป็นปัญหาสำหรับตัวแยกวิเคราะห์ที่เข้มงวดมาก
nimish

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

ฉันใช้การร้องขอเวอร์ชัน2.18.4และ Python 3


44

หมายเหตุ: คำตอบนี้ล้าสมัยแล้ว รุ่นใหม่ของrequests การสนับสนุนการรับเนื้อหาขอร้องโดยตรงเช่นAntonioHerraizS คำตอบของเอกสาร

มันเป็นไปไม่ได้ที่จะได้รับความจริงเนื้อหาดิบของการร้องขอออกจากrequestsเนื่องจากข้อตกลงเฉพาะกับวัตถุระดับที่สูงขึ้นเช่นส่วนหัวและประเภทวิธีการ requestsใช้urllib3เพื่อส่งคำขอ แต่urllib3 ยังไม่ได้จัดการกับข้อมูลดิบ - httplibมันใช้ นี่คือการติดตามสแต็กตัวแทนของคำขอ:

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

ภายในhttplibเครื่องจักรเราสามารถเห็นการHTTPConnection._send_requestใช้ทางอ้อมHTTPConnection._send_outputซึ่งในที่สุดก็สร้างคำขอและตัววัตถุดิบ(ถ้ามี) และใช้HTTPConnection.sendเพื่อส่งแยกต่างหาก sendในที่สุดก็ถึงซ็อกเก็ต

เนื่องจากไม่มีตะขอสำหรับการทำสิ่งที่คุณต้องการเป็นทางเลือกสุดท้ายคุณสามารถลิงปะhttplibเพื่อรับเนื้อหา มันเป็นทางออกที่บอบบางและคุณอาจจำเป็นต้องปรับเปลี่ยนหากhttplibมีการเปลี่ยนแปลง หากคุณตั้งใจจะแจกจ่ายซอฟต์แวร์โดยใช้วิธีแก้ปัญหานี้คุณอาจต้องการพิจารณาบรรจุภัณฑ์httplibแทนการใช้ระบบซึ่งง่ายเพราะเป็นโมดูลหลามบริสุทธิ์

อนิจจาโดยไม่ต้องกังวลใจต่อการแก้ปัญหา:

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

ซึ่งให้ผลผลิต:

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 ถ้าฉันเรียกขั้นตอน patch_send () เป็นครั้งที่ 2 (หลังจากคำขอที่ 2) แล้วมันจะพิมพ์ข้อมูลสองครั้ง (ดังนั้น 2x เท่าของเอาต์พุตที่คุณได้แสดงไว้ด้านบน)? ดังนั้นถ้าฉันทำตามคำขอครั้งที่ 3 มันจะพิมพ์ออกมา 3 ครั้งและต่อไป ... ความคิดใดที่จะได้ผลลัพธ์เพียงครั้งเดียว? ขอบคุณล่วงหน้า.
opstalj

@opstalj คุณไม่ควรเรียกpatch_sendหลายครั้งเพียงครั้งเดียวหลังจากการนำเข้าhttplib
goncalopp

40

แนวคิดที่ดียิ่งขึ้นคือการใช้ไลบรารี requests_toolbelt ซึ่งสามารถทิ้งการร้องขอและการตอบกลับเป็นสตริงเพื่อให้คุณพิมพ์ไปยังคอนโซล มันจัดการกับทุกกรณีหากินด้วยไฟล์และการเข้ารหัสซึ่งวิธีการแก้ปัญหาข้างต้นไม่สามารถจัดการได้ดี

ง่ายเหมือนอย่างนี้:

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

ที่มา: https://toolbelt.readthedocs.org/en/latest/dumputils.html

คุณสามารถติดตั้งได้โดยพิมพ์:

pip install requests_toolbelt

2
นี่ดูเหมือนจะไม่ถ่ายโอนคำขอโดยไม่ส่งก็ตาม
Dobes Vandermeer

1
dump_all ไม่ทำงานอย่างถูกต้องเนื่องจากฉันได้รับ "TypeError: ไม่สามารถต่อกัน 'str' และวัตถุ 'UUID'" จากการโทร
rtaft

@rtaft: โปรดรายงานว่านี่เป็นข้อผิดพลาดในที่เก็บ github ของพวกเขา: github.com/sigmavirus24/requests-toolbelt/
......

มันพิมพ์ดัมพ์ด้วย> และ <สัญญาณว่าเป็นส่วนหนึ่งของคำขอจริงหรือไม่?
Jay

1
@Jay ดูเหมือนว่าพวกเขาจะถูกผนวกเข้ากับคำขอ / การตอบสนองที่แท้จริงสำหรับลักษณะที่ปรากฏ ( github.com/requests/toolbelt/blob/master/requests_toolbelt/ … ) และสามารถระบุได้โดยส่ง request_prefix = b '{some_request_prefix}', response_prefix = ' b '{some_response_prefix}' ถึง dump_all ( github.com/requests/toolbelt/blob/master/requests_toolbelt/ ...... )
Christian Reall-Fluharty

7

นี่คือรหัสซึ่งทำเหมือนกัน แต่มีส่วนหัวการตอบกลับ:

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

ฉันใช้เวลามากมายในการค้นหาสิ่งนี้ดังนั้นฉันจึงออกจากที่นี่ถ้ามีคนต้องการ


4

ฉันใช้ฟังก์ชั่นต่อไปนี้เพื่อจัดรูปแบบคำขอ มันเหมือน @AntonioHerraizS ยกเว้นว่ามันจะพิมพ์วัตถุ JSON ในร่างกายได้เป็นอย่างดีและมันก็ติดฉลากทุกส่วนของคำขอ

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

และฉันมีฟังก์ชั่นที่คล้ายกันเพื่อจัดรูปแบบการตอบสนอง:

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

requestsรองรับที่เรียกว่าเบ็ดเหตุการณ์ (ณ วันที่ 2.23 มีเพียงresponseตะขอจริง ๆ) เบ็ดสามารถใช้กับการร้องขอเพื่อพิมพ์ข้อมูลของคู่การร้องขอการตอบสนองเต็มรูปแบบรวมถึง URL ที่มีประสิทธิภาพส่วนหัวและร่างกายเช่น:

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

เรียกใช้พิมพ์:

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

คุณอาจต้องการที่จะเปลี่ยนres.textไปres.contentถ้าตอบเป็นเลขฐานสอง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.