使用requests
module时,有没有办法打印原始HTTP请求?
我不想只是标题,我想要请求行,标题和内容打印输出。是否有可能看到最终由HTTP请求构建的内容?
答案 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 requests和API - 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!