Pythonリクエスト-httpリクエスト全体を印刷しますか(raw)?


197

requestsモジュールを使用しているときに、生のHTTPリクエストを出力する方法はありますか?

ヘッダーだけでなく、リクエストライン、ヘッダー、コンテンツのプリントアウトも必要です。最終的にHTTPリクエストから何が構築されるかを確認することは可能ですか?


9
@RickyA彼は応答ではなく要求の内容について尋ねています
goncalopp

2
それは良い質問です。ソースを見ると、準備されたリクエストの生のコンテンツを取得する方法はないようで、送信時にのみシリアル化されます。それは良い機能のようです。
ティムピアス

まあ、あなたはwiresharkを起動して、それをそのように見ることもできます。
RickyA 2013

@qwrrtyそれとしてこれを統合することは困難であろうrequestsことはrewritting /バイパスを意味するであろうとして、機能urllib3httplib。以下のスタックトレースを参照してください
goncalopp

これは私のために働いた- stackoverflow.com/questions/10588644/...
アジャイ

回答:


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

68
単純なresponse = requests.post(...)(またはrequests.getor requests.put、など)メソッドを使用する場合、実際にPreparedResponse通過することができますresponse.request。応答を受信する前に生のhttpデータにアクセスする必要がない場合は、requests.Requestとを手動で操作する作業を節約できますrequests.Session
Gershom 2015年

2
いい答えだ。ただし、HTTPの改行は\ r \ nだけではなく\ nにする必要があることを更新する必要があるかもしれません。
ltc

3
URLの直後のHTTPプロトコルバージョン部分はどうですか?'HTTP / 1.1'のように?あなたのきれいなプリンターを使用して印刷するときそれは見つけられません。
サジューク、

1
RFC 2616が必要とするもののため、CRLFを使用するように更新され、それは非常に厳格なパーサーの問題である可能性があり
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

注:この回答は古くなっています。AntonioHerraizSの回答ドキュメントのrequests ように、リクエストのコンテンツを直接取得する新しいバージョンのサポート

ヘッダーメソッドタイプなどの上位レベルのオブジェクトしか処理しないため、からリクエストの真の生のコンテンツを取得することはできません。requestsrequests用途は、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_outputHTTPConnection.sendsend

やりたいことをするためのフックがないので、最後の手段として、パッチ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

こんにちはgoncalopp、私がpatch_send()プロシージャを2回呼び出した場合(2回目のリクエストの後)、データを2回出力します(つまり、上記の出力の2倍)。したがって、3番目のリクエストを実行すると、3回印刷されるというようになります...出力を1回だけ取得する方法はありますか?前もって感謝します。
opstalj

@opstalj patch_sendインポート後に複数回呼び出すことはできません。一度だけ呼び出してくださいhttplib
goncalopp '29

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

1
呼び出しから「TypeError: 'str'および 'UUID'オブジェクトを連結できません」と表示されるため、dump_allが正しく機能していないようです。
rtaft 2016年

@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、応答がバイナリである場合。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.