Yêu cầu Python - in toàn bộ yêu cầu http (thô)?


197

Trong khi sử dụng requestsmô-đun , có cách nào để in yêu cầu HTTP thô không?

Tôi không chỉ muốn các tiêu đề, tôi muốn dòng yêu cầu, tiêu đề và bản in nội dung. Có thể xem những gì cuối cùng được xây dựng từ yêu cầu HTTP?


9
@RickyA anh ấy hỏi về nội dung của yêu cầu, không phải câu trả lời
goncalopp

2
Đó là một câu hỏi hay. Từ việc xem xét nguồn, có vẻ như không có cách nào để có được nội dung thô của một yêu cầu được chuẩn bị và nó chỉ được đăng theo thứ tự khi nó được gửi. Có vẻ như nó sẽ là một tính năng tốt.
Tim Pierce

Chà, bạn cũng có thể bắt đầu wireshark và thấy nó theo cách đó.
RickyA

@qwrrty sẽ khó tích hợp requeststính năng này như một tính năng, vì nó có nghĩa là viết lại / bỏ qua urllib3httplib. Xem dấu vết ngăn xếp bên dưới
goncalopp

Điều này làm việc cho tôi - stackoverflow.com/questions/10588644/ từ
Ajay

Câu trả lời:


213

Kể từ v1.2.3 Yêu cầu đã thêm đối tượng PreparedRequest. Theo tài liệu "nó chứa các byte chính xác sẽ được gửi đến máy chủ".

Người ta có thể sử dụng điều này để in một yêu cầu, như vậy:

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)

sản xuất:

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

a=1&b=2

Sau đó, bạn có thể gửi yêu cầu thực tế với điều này:

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

Các liên kết này là tài liệu mới nhất hiện có, vì vậy chúng có thể thay đổi về nội dung: Nâng cao - Yêu cầu được chuẩn bịAPI - Các lớp cấp thấp hơn


2
Điều này mạnh hơn nhiều so với phương pháp vá khỉ của tôi. Nâng cấp requestsrất đơn giản, vì vậy tôi nghĩ rằng điều này sẽ trở thành câu trả lời được chấp nhận
goncalopp

68
Nếu bạn sử dụng đơn giản response = requests.post(...)(hoặc requests.gethoặc requests.put, vv) phương pháp, bạn thực sự có thể nhận được PreparedResponsethông qua response.request. Nó có thể lưu công việc thao tác thủ công requests.Requestrequests.Session, nếu bạn không cần truy cập dữ liệu http thô trước khi nhận được phản hồi.
Gershom

2
Câu trả lời tốt. Một điều bạn có thể muốn cập nhật là dòng ngắt trong HTTP phải là \ r \ n không chỉ là \ n.
ltc

3
Còn phần phiên bản giao thức HTTP chỉ sau url thì sao? như 'HTTP / 1.1'? không tìm thấy khi in ra bằng máy in đẹp của bạn.
Sajuuk

1
Đã cập nhật để sử dụng CRLF, vì đó là những gì RFC 2616 yêu cầu và nó có thể là một vấn đề đối với các trình phân tích cú pháp rất nghiêm ngặt
bắt đầu từ

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

Tôi đang sử dụng yêu cầu phiên bản 2.18.4 và Python 3


44

Lưu ý: câu trả lời này đã lỗi thời. Các phiên bản mới hơn của requests hỗ trợ nhận trực tiếp nội dung yêu cầu, như tài liệu trả lời của AntonioHerraizS .

Không thể lấy nội dung thô thực sự của yêu cầu requests, vì nó chỉ xử lý các đối tượng cấp cao hơn, chẳng hạn như tiêu đềloại phương thức . requestssử dụng urllib3để gửi yêu cầu, nhưng urllib3 cũng không xử lý dữ liệu thô - nó sử dụng httplib. Đây là dấu vết ngăn xếp đại diện của một yêu cầu:

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

Bên trong httplibmáy móc, chúng ta có thể thấy các HTTPConnection._send_requestcách sử dụng gián tiếp HTTPConnection._send_output, cuối cùng tạo ra yêu cầu thô cơ thể (nếu nó tồn tại) và sử dụng HTTPConnection.sendđể gửi chúng một cách riêng biệt. sendcuối cùng cũng đến được ổ cắm.

Vì không có móc nối để làm những gì bạn muốn, như là phương sách cuối cùng, bạn có thể vá khỉ httplibđể có được nội dung. Đó là một giải pháp dễ vỡ và bạn có thể cần điều chỉnh nó nếu httplibbị thay đổi. Nếu bạn có ý định phân phối phần mềm bằng giải pháp này, bạn có thể muốn xem xét việc đóng gói httplibthay vì sử dụng hệ thống, điều này thật dễ dàng, vì đây là mô-đun python thuần.

Than ôi, không có thêm ado, giải pháp:

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

mang lại sản lượng:

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

Xin chào goncalopp, nếu tôi gọi thủ tục patch_send () lần thứ 2 (sau yêu cầu thứ 2), thì nó sẽ in dữ liệu hai lần (gấp 2 lần đầu ra như bạn đã trình bày ở trên)? Vì vậy, nếu tôi thực hiện yêu cầu thứ 3, nó sẽ in nó gấp 3 lần và cứ thế ... Bạn có biết làm thế nào để chỉ nhận được đầu ra một lần không? Cảm ơn trước.
opstalj

@opstalj bạn không nên gọi patch_sendnhiều lần, chỉ một lần, sau khi nhậphttplib
goncalopp

40

Một ý tưởng tốt hơn nữa là sử dụng thư viện request_toolbelt, có thể loại bỏ cả yêu cầu và phản hồi dưới dạng chuỗi để bạn in ra bàn điều khiển. Nó xử lý tất cả các trường hợp khó khăn với các tệp và mã hóa mà giải pháp trên không xử lý tốt.

Nó dễ như thế này:

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

Nguồn: https://toolbelt.readthedocs.org/en/latest/dumputils.html

Bạn chỉ có thể cài đặt nó bằng cách gõ:

pip install requests_toolbelt

2
Điều này dường như không bỏ yêu cầu mà không gửi nó, mặc dù.
Dobes Vandermeer

1
dump_all dường như không hoạt động chính xác khi tôi nhận được "TypeError: không thể ghép các đối tượng 'str' và 'UUID' từ cuộc gọi.
rtaft

@rtaft: Vui lòng báo cáo đây là lỗi trong kho github của họ: github.com/sigmavirus24/requests-toolbelt/ trộm
Emil Stenström

Nó in kết xuất với dấu> và <, chúng có phải là một phần của yêu cầu thực tế không?
Jay

1
@ Jay Dường như họ đang thêm vào phía trước yêu cầu thực tế / phản ứng với sự xuất hiện ( github.com/requests/toolbelt/blob/master/requests_toolbelt/... ) và có thể được xác định bằng cách thông qua 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

Đây là một mã, làm cho giống nhau, nhưng với các tiêu đề phản hồi:

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

Tôi đã dành rất nhiều thời gian để tìm kiếm cái này, vì vậy tôi sẽ để nó ở đây, nếu ai đó cần.


4

Tôi sử dụng các chức năng sau đây để định dạng yêu cầu. Nó giống như @AntonioHerraizS ngoại trừ nó cũng sẽ in các đối tượng JSON trong cơ thể và nó dán nhãn tất cả các phần của yêu cầu.

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

Và tôi có một chức năng tương tự để định dạng phản hồi:

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

requestshỗ trợ cái gọi là hook sự kiện (tính đến 2.23 thực tế chỉ có responsehook). Móc có thể được sử dụng theo yêu cầu để in dữ liệu của cặp phản hồi yêu cầu đầy đủ, bao gồm URL, tiêu đề và nội dung hiệu quả, như:

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

Chạy nó in:

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

Bạn có thể muốn thay đổi res.textđể res.contentnếu câu trả lời là nhị phân.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.