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

时间:2013-12-18 12:43:03

标签: python http python-requests

使用requests module时,有没有办法打印原始HTTP请求?

我不想只是标题,我想要请求行,标题和内容打印输出。是否有可能看到最终由HTTP请求构建的内容?

9 个答案:

答案 0 :(得分:157)

Since 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{}\n{}\n\n{}'.format(
        '-----------START-----------',
        req.method + ' ' + req.url,
        '\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)

这些链接指向最新的文档,因此内容可能会发生变化: Advanced - Prepared requestsAPI - Lower level classes

答案 1 :(得分:41)

注意:这个答案已经过时了。较新版本的requests支持直接获取请求内容,如AntonioHerraizS's answer个文档

无法从requests中获取请求的 true 原始内容,因为它只处理更高级别的对象,例如 headers 方法类型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模块。

唉,不用多说,解决方案:

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

答案 2 :(得分:30)

更好的想法是使用requests_toolbelt库,它可以将请求和响应转储为字符串,以便您打印到控制台。它使用上述解决方案无法处理的文件和编码来处理所有棘手的情况。

这很容易:

TM_COMMENT_END_2

来源:https://toolbelt.readthedocs.org/en/latest/dumputils.html

您只需输入以下命令即可安装:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>name</key>
    <string>Comments</string>
    <key>scope</key>
    <string>source.python</string>
    <key>settings</key>
    <dict>
        <key>shellVariables</key>
        <array>
            <dict>
                <key>name</key>
                <string>TM_COMMENT_START</string>
                <key>value</key>
                <string># </string>
            </dict>
            <dict>
                <key>name</key>
                <string>TM_COMMENT_START_2</string>
                <key>value</key>
                <string>"""</string>
            </dict>
            <dict>
                <key>name</key>
                <string>TM_COMMENT_END_2</string>
                <key>value</key>
                <string>"""</string>
            </dict>
        </array>
    </dict>
    <key>uuid</key>
    <string>6550FEAD-D547-44E4-84F7-7D421D6078B0</string>
</dict>
</plist>

答案 3 :(得分:24)

Missing import statement less... (Ctrl+F1) 
Checks that all modules are referenced through import statements.
Suggests inserting the import statement.
When using libraries that define their own global symbols outside their visible JavaScript code (e.g. describe() in Mocha), it is recommended that you add the corresponding TypeScript type definition file as a JavaScript library in Preferences | Languages & Frameworks | JavaScript | Libraries.

我正在使用请求版本 2.18.4 和Python 3

答案 4 :(得分:6)

这是一个代码,它使相同,但带有响应标题:

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

我花了很多时间寻找这个,所以如果有人需要,我就把它留在这里。

答案 5 :(得分:5)

requests支持所谓的event hooks(从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

答案 6 :(得分:2)

我使用以下功能来格式化请求。就像@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

答案 7 :(得分:1)

A fork of @AntonioHerraizS answer(HTTP版本缺少注释中所述)


使用以下代码获取表示原始HTTP数据包的字符串,而不发送它:

POST / HTTP/1.1
User-Agent: Test
Content-Length: 18
Content-Type: application/json

{"hello": "world"}

结果:

r = requests.get('https://stackoverflow.com')
raw_request = get_raw_request(r.request)
print(raw_request)

?也可以在响应对象中打印请求

:check
for /f "tokens=2 delims==" %%d in ('wmic logicaldisk where "drivetype=3" get name /format:value') do (
set vvv=%%d
for /f "tokens=1,*" %%A in ('manage-bde -status %vvv% ^| findstr Conversion') do set var1=%%B
Rem  Try to find if value is Encrypted or not
echo %vb1%|find "Encrypted" >nul
if errorlevel 1 ( goto :check) else ( goto :decrypt_c))

答案 8 :(得分:0)

test_print.py内容:

import logging
import pytest
import requests
from requests_toolbelt.utils import dump


def print_raw_http(response):
    data = dump.dump_all(response, request_prefix=b'', response_prefix=b'')
    return '\n' * 2 + data.decode('utf-8')

@pytest.fixture
def logger():
    log = logging.getLogger()
    log.addHandler(logging.StreamHandler())
    log.setLevel(logging.DEBUG)
    return log

def test_print_response(logger):
    session = requests.Session()
    response = session.get('http://127.0.0.1:5000/')
    assert response.status_code == 300, logger.warning(print_raw_http(response))

hello.py内容:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

运行:

 $ python -m flask hello.py
 $ python -m pytest test_print.py

标准输出:

------------------------------ Captured log call ------------------------------
DEBUG    urllib3.connectionpool:connectionpool.py:225 Starting new HTTP connection (1): 127.0.0.1:5000
DEBUG    urllib3.connectionpool:connectionpool.py:437 http://127.0.0.1:5000 "GET / HTTP/1.1" 200 13
WARNING  root:test_print_raw_response.py:25 

GET / HTTP/1.1
Host: 127.0.0.1:5000
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive


HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 13
Server: Werkzeug/1.0.1 Python/3.6.8
Date: Thu, 24 Sep 2020 21:00:54 GMT

Hello, World!