Python请求-打印整个HTTP请求(原始)?


197

在使用requests模块时,有什么方法可以打印原始HTTP请求?

我不仅想要标题,还想要请求行,标题和内容打印输出。是否有可能看到HTTP请求最终构造了什么?


9
@RickyA,他询问的是请求的内容,而不是响应
goncalopp 2013年

2
这是个好问题。从源头上看,似乎没有任何方法可以获取准备好的请求的原始内容,并且仅在发送时才进行序列化。看来这将是一个不错的功能。
Tim Pierce 2013年

好吧,您也可以启动wireshark并以这种方式查看它。
RickyA 2013年

@qwrrty很难将其集成为requests功能,因为这将意味着重写/绕过urllib3and httplib。请参阅下面的堆栈跟踪记录
goncalopp 2013年

Answers:


213

从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 2014年

68
如果使用简单的response = requests.post(...)(或requests.getrequests.put等)方法,则实际上可以PreparedResponse通过response.request。如果您不需要在收到响应之前访问原始http数据,则可以节省手动操作requests.Request和的工作requests.Session
Gershom 2015年

2
好答案。不过,您可能要更新的一件事是HTTP中的换行符应该是\ r \ n而不只是\ n。
ltc

3
网址后面的HTTP协议版本部分呢?像“ HTTP / 1.1”?使用漂亮的打印机打印出来时找不到。
Sajuuk

1
更新为使用CRLF,因为这正是2616需要,它可能是非常严格的解析器的问题
nimish

55
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而不是使用系统包,这很容易,因为它是纯python模块。

las,不费吹灰之力,解决方案:

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倍)?因此,如果我要执行第三个请求,它将打印3次,依此类推...任何想法如何只获取一次输出?提前致谢。
opstalj

@opstalj patch_send导入后,您不应多次调用,只能调用一次httplib
goncalopp 2015年

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

1
当我从调用中收到“ TypeError:无法连接'str'和'UUID'对象”时,dump_all似乎无法正常工作。
rtaft

:@rtaft:请报告此作为他们的github仓库中的错误github.com/sigmavirus24/requests-toolbelt/...
周华健Stenström

它用>和<符号打印转储,它们是实际请求的一部分吗?
周杰伦

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.textres.content则可能需要更改为。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.