Last active
December 28, 2021 13:05
-
-
Save angeloped/561e021e6c79c97071f4628e118fbd91 to your computer and use it in GitHub Desktop.
A fork of dump.py from the requests_toolbelt `requests` utility extension.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""This module provides functions for dumping information about responses.""" | |
""" | |
This is a fork of dump.py from requests_toolbelt. | |
https://github.com/requests/toolbelt/blob/master/requests_toolbelt/utils/dump.py | |
""" | |
import collections | |
from requests import compat | |
__all__ = ('dump_response', 'dump_all') | |
HTTP_VERSIONS = { | |
9: b'0.9', | |
10: b'1.0', | |
11: b'1.1', | |
} | |
_PrefixSettings = collections.namedtuple('PrefixSettings', | |
['request', 'response']) | |
class PrefixSettings(_PrefixSettings): | |
def __new__(cls, request, response): | |
request = _coerce_to_bytes(request) | |
response = _coerce_to_bytes(response) | |
return super(PrefixSettings, cls).__new__(cls, request, response) | |
def _get_proxy_information(response): | |
if getattr(response.connection, 'proxy_manager', False): | |
proxy_info = {} | |
request_url = response.request.url | |
if request_url.startswith('https://'): | |
proxy_info['method'] = 'CONNECT' | |
proxy_info['request_path'] = request_url | |
return proxy_info | |
return None | |
def _format_header(name, value): | |
return (_coerce_to_bytes(name) + b': ' + _coerce_to_bytes(value) + | |
b'\r\n') | |
def _build_request_path(url, proxy_info): | |
uri = compat.urlparse(url) | |
proxy_url = proxy_info.get('request_path') | |
if proxy_url is not None: | |
request_path = _coerce_to_bytes(proxy_url) | |
return request_path, uri | |
request_path = _coerce_to_bytes(uri.path) | |
if uri.query: | |
request_path += b'?' + _coerce_to_bytes(uri.query) | |
return request_path, uri | |
def _dump_request_data(request, prefixes, bytearr, proxy_info=None): | |
if proxy_info is None: | |
proxy_info = {} | |
prefix = prefixes.request | |
method = _coerce_to_bytes(proxy_info.pop('method', request.method)) | |
request_path, uri = _build_request_path(request.url, proxy_info) | |
# <prefix><METHOD> <request-path> HTTP/1.1 | |
bytearr.extend(prefix + method + b' ' + request_path + b' HTTP/1.1\r\n') | |
# <prefix>Host: <request-host> OR host header specified by user | |
headers = request.headers.copy() | |
host_header = _coerce_to_bytes(headers.pop('Host', uri.netloc)) | |
bytearr.extend(prefix + b'Host: ' + host_header + b'\r\n') | |
for name, value in headers.items(): | |
bytearr.extend(prefix + _format_header(name, value)) | |
bytearr.extend(prefix + b'\r\n') | |
if request.body: | |
if isinstance(request.body, compat.basestring): | |
bytearr.extend(prefix + _coerce_to_bytes(request.body)) | |
else: | |
# In the event that the body is a file-like object, let's not try | |
# to read everything into memory. | |
bytearr.extend(b'<< Request body is not a string-like type >>') | |
bytearr.extend(b'\r\n') | |
bytearr.extend(b'\r\n') | |
def _dump_response_data(response, prefixes, bytearr): | |
prefix = prefixes.response | |
# Let's interact almost entirely with urllib3's response | |
raw = response.raw | |
# Let's convert the version int from httplib to bytes | |
version_str = HTTP_VERSIONS.get(raw.version, b'?') | |
# <prefix>HTTP/<version_str> <status_code> <reason> | |
bytearr.extend(prefix + b'HTTP/' + version_str + b' ' + | |
str(raw.status).encode('ascii') + b' ' + | |
_coerce_to_bytes(response.reason) + b'\r\n') | |
headers = raw.headers | |
for name in headers.keys(): | |
for value in headers.getlist(name): | |
bytearr.extend(prefix + _format_header(name, value)) | |
bytearr.extend(prefix + b'\r\n') | |
bytearr.extend(response.content) | |
def _coerce_to_bytes(data): | |
if not isinstance(data, bytes) and hasattr(data, 'encode'): | |
data = data.encode('utf-8') | |
# Don't bail out with an exception if data is None | |
return data if data is not None else b'' | |
def dump_response(response, request_prefix=b'< ', response_prefix=b'> ', | |
data_array=None): | |
"""Dump a single request-response cycle's information. | |
This will take a response object and dump only the data that requests can | |
see for that single request-response cycle. | |
Example:: | |
import requests | |
from requests_toolbelt.utils import dump | |
resp = requests.get('https://api.github.com/users/sigmavirus24') | |
data = dump.dump_response(resp) | |
print(data.decode('utf-8')) | |
:param response: | |
The response to format | |
:type response: :class:`requests.Response` | |
:param request_prefix: (*optional*) | |
Bytes to prefix each line of the request data | |
:type request_prefix: :class:`bytes` | |
:param response_prefix: (*optional*) | |
Bytes to prefix each line of the response data | |
:type response_prefix: :class:`bytes` | |
:param data_array: (*optional*) | |
Bytearray to which we append the request-response cycle data | |
:type data_array: :class:`bytearray` | |
:returns: Formatted bytes of request and response information. | |
:rtype: :class:`bytearray` | |
""" | |
data = data_array if data_array is not None else bytearray() | |
prefixes = PrefixSettings(request_prefix, response_prefix) | |
if not hasattr(response, 'request'): | |
raise ValueError('Response has no associated request') | |
proxy_info = _get_proxy_information(response) | |
_dump_request_data(response.request, prefixes, data, | |
proxy_info=proxy_info) | |
_dump_response_data(response, prefixes, data) | |
return data | |
def dump_all(response, request_prefix=b'< ', response_prefix=b'> '): | |
"""Dump all requests and responses including redirects. | |
This takes the response returned by requests and will dump all | |
request-response pairs in the redirect history in order followed by the | |
final request-response. | |
Example:: | |
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')) | |
:param response: | |
The response to format | |
:type response: :class:`requests.Response` | |
:param request_prefix: (*optional*) | |
Bytes to prefix each line of the request data | |
:type request_prefix: :class:`bytes` | |
:param response_prefix: (*optional*) | |
Bytes to prefix each line of the response data | |
:type response_prefix: :class:`bytes` | |
:returns: Formatted bytes of request and response information. | |
:rtype: :class:`bytearray` | |
""" | |
data = bytearray() | |
history = list(response.history[:]) | |
history.append(response) | |
for response in history: | |
dump_response(response, request_prefix, response_prefix, data) | |
return data |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment