파이썬 요청-전체 http 요청을 인쇄합니까?


197

requests모듈을 사용하는 동안 원시 HTTP 요청을 인쇄하는 방법이 있습니까?

헤더 만 원하지 않고 요청 줄, 헤더 및 내용 인쇄물을 원합니다. 궁극적으로 HTTP 요청으로 구성되는 것을 볼 수 있습니까?


9
@RickyA 그는 응답이 아닌 요청의 내용에 대해 묻습니다
goncalopp

2
그건 좋은 질문이야. 소스를 살펴보면 준비된 요청의 원시 컨텐츠를 얻는 방법이없는 것처럼 보이며 전송 될 때만 직렬화됩니다. 좋은 기능인 것 같습니다.
Tim Pierce

글쎄, 당신은 wireshark를 시작해서 그렇게 볼 수도 있습니다.
RickyA

@qwrrty이 requests기능을 기능 으로 통합하는 것은 어려울 것 입니다. 다시 쓰기 / 우회 urllib3와를 의미하기 때문입니다 httplib. 아래의 스택 추적을 참조하십시오
goncalopp

이것은 나를 위해 일한 - stackoverflow.com/questions/10588644/...
아제

답변:


215

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

69
당신이 간단한을 사용하는 경우 response = requests.post(...)(또는 requests.get또는 requests.put등) 방법, 당신은 실제로 얻을 수 있습니다 PreparedResponse통해를 response.request. 응답을 받기 전에 원시 http 데이터에 액세스 할 필요가없는 경우 수동 조작 requests.Request및 의 작업을 저장할 수 있습니다 requests.Session.
Gershom

2
좋은 대답입니다. 업데이트하고 싶은 한 가지는 HTTP의 줄 바꿈이 \ n이 아니라 \ r \ n이어야한다는 것입니다.
ltc

3
URL 바로 다음의 HTTP 프로토콜 버전 부분은 어떻습니까? 'HTTP / 1.1'과 같은? 예쁜 프린터를 사용하여 인쇄 할 때 찾을 수 없습니다.
사주

1
RFC 2616이 요구하는 그 무엇의 이후, CRLF를 사용하도록 업데이트, 그것은 매우 엄격한 파서에 대한 문제가 될 수
nimish

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

내가 사용하고 요청 버전 2.18.4 파이썬 3


44

참고 :이 답변은 구식입니다. 최신 버전의 requests 직접적 요청 내용을지고 지원, AntonioHerraizS의 응답 문서 .

헤더메소드 유형 과 같은 상위 레벨 오브젝트 만 처리하므로 요청 의 실제 원시 컨텐츠 를 가져올 수 없습니다 . 용도는 요청을 보내지 만합니다 또한 원시 데이터를 처리하지 않습니다 - 그것은 사용합니다 . 요청의 대표적인 스택 추적은 다음과 같습니다.requestsrequestsurllib3urllib3 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간접적으로 uses을 볼 수 있으며 HTTPConnection._send_output, 이는 최종적으로 원시 요청 본문을 작성하고 (있는 경우) HTTPConnection.send개별적으로 전송하는 데 사용 됩니다. send마침내 소켓에 도달합니다.

원하는 것을 수행하기위한 후크가 없기 때문에 마지막 수단으로 원숭이 패치 httplib를 사용하여 내용을 얻을 수 있습니다. 깨지기 쉬운 솔루션 httplib이므로 변경 하면 수정해야 할 수도 있습니다 . 이 솔루션을 사용하여 소프트웨어를 배포하려는 경우 httplib순수한 파이썬 모듈이므로 시스템을 사용하는 대신 패키징을 고려할 수 있습니다 .

아아, 더 이상 고민하지 않고 해결책 :

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 배 배)? 따라서 세 번째 요청을하면 3x 번 인쇄합니다. 출력을 한 번만 얻는 방법에 대한 아이디어가 있습니까? 미리 감사드립니다.
opstalj

@opstalj 당신은 patch_send가져 오기 후 여러 번, 한 번만 호출해서는 안됩니다httplib
goncalopp

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

@rtaft : 자신의 GitHub의 저장소에 버그로 신고 해주세요 : github.com/sigmavirus24/requests-toolbelt/...
에밀 Stenström

덤프는> 및 <기호로 인쇄합니다. 실제 요청의 일부입니까?
Jay

1
@Jay 모양에 대한 실제 요청 / 응답 앞에 추가 된 것으로 보이며 ( github.com/requests/toolbelt/blob/master/requests_toolbelt/… ) request_prefix = b '{some_request_prefix}', response_prefix = b '{some_response_prefix}'to 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

다음 함수를 사용하여 요청을 형식화합니다. 본문에 JSON 객체를 예쁘게 인쇄하고 요청의 모든 부분에 레이블을 지정한다는 점을 제외하면 @AntonioHerraizS와 같습니다.

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.