1
0
mirror of https://github.com/httpie/cli.git synced 2026-04-26 20:02:11 +02:00
Files
httpie-cli/httpie/client.py
T

281 lines
8.5 KiB
Python
Raw Normal View History

2019-08-31 17:52:56 +02:00
import argparse
import http.client
import json
import sys
2019-08-31 17:52:56 +02:00
import zlib
from contextlib import contextmanager
from pathlib import Path
from typing import Iterable, Union
import requests
2016-03-02 12:12:05 +08:00
from requests.adapters import HTTPAdapter
from httpie import __version__
2019-08-31 15:17:10 +02:00
from httpie.cli.constants import SSL_VERSION_ARG_MAPPING
2019-08-31 17:52:56 +02:00
from httpie.cli.dicts import RequestHeadersDict
from httpie.plugins import plugin_manager
from httpie.sessions import get_httpie_session
2019-08-31 18:00:03 +02:00
from httpie.utils import repr_dict
2019-08-29 10:44:59 +02:00
try:
2017-12-28 18:32:12 +01:00
# noinspection PyPackageRequirements
import urllib3
2019-09-01 11:38:14 +02:00
# <https://urllib3.readthedocs.io/en/latest/security.html>
urllib3.disable_warnings()
2017-12-28 18:32:12 +01:00
except (ImportError, AttributeError):
pass
2016-07-02 14:18:36 +02:00
FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded; charset=utf-8'
JSON_CONTENT_TYPE = 'application/json'
2019-08-31 15:17:10 +02:00
JSON_ACCEPT = f'{JSON_CONTENT_TYPE}, */*'
2019-08-31 17:52:56 +02:00
DEFAULT_UA = f'HTTPie/{__version__}'
def collect_messages(
args: argparse.Namespace,
config_dir: Path,
) -> Iterable[Union[requests.PreparedRequest, requests.Response]]:
httpie_session = None
httpie_session_headers = None
if args.session or args.session_read_only:
httpie_session = get_httpie_session(
config_dir=config_dir,
session_name=args.session or args.session_read_only,
host=args.headers.get('Host'),
url=args.url,
)
httpie_session_headers = httpie_session.headers
request_kwargs = make_request_kwargs(
args=args,
base_headers=httpie_session_headers,
)
send_kwargs = make_send_kwargs(args)
send_kwargs_mergeable_from_env = make_send_kwargs_mergeable_from_env(args)
requests_session = build_requests_session(
ssl_version=args.ssl_version,
)
if httpie_session:
httpie_session.update_headers(request_kwargs['headers'])
requests_session.cookies = httpie_session.cookies
if args.auth_plugin:
# Save auth from CLI to HTTPie session.
httpie_session.auth = {
'type': args.auth_plugin.auth_type,
'raw_auth': args.auth_plugin.raw_auth,
}
elif httpie_session.auth:
# Apply auth from HTTPie session
request_kwargs['auth'] = httpie_session.auth
if args.debug:
# TODO: reflect the split between request and send kwargs.
dump_request(request_kwargs)
request = requests.Request(**request_kwargs)
prepared_request = requests_session.prepare_request(request)
2019-09-04 00:00:03 +02:00
if args.compress and prepared_request.body:
compress_body(prepared_request, always=args.compress > 1)
response_count = 0
while prepared_request:
yield prepared_request
if not args.offline:
send_kwargs_merged = requests_session.merge_environment_settings(
url=prepared_request.url,
**send_kwargs_mergeable_from_env,
)
with max_headers(args.max_headers):
response = requests_session.send(
request=prepared_request,
**send_kwargs_merged,
**send_kwargs,
)
response_count += 1
if response.next:
if args.max_redirects and response_count == args.max_redirects:
raise requests.TooManyRedirects
if args.follow:
prepared_request = response.next
if args.all:
yield response
continue
yield response
break
if httpie_session:
if httpie_session.is_new() or not args.session_read_only:
httpie_session.cookies = requests_session.cookies
httpie_session.save()
# noinspection PyProtectedMember
@contextmanager
def max_headers(limit):
# <https://github.com/jakubroztocil/httpie/issues/802>
orig = http.client._MAXHEADERS
http.client._MAXHEADERS = limit or float('Inf')
try:
yield
finally:
http.client._MAXHEADERS = orig
2019-09-04 00:00:03 +02:00
def compress_body(request: requests.PreparedRequest, always: bool):
deflater = zlib.compressobj()
body_bytes = (
request.body
if isinstance(request.body, bytes)
else request.body.encode()
)
deflated_data = deflater.compress(body_bytes)
deflated_data += deflater.flush()
is_economical = len(deflated_data) < len(body_bytes)
if is_economical or always:
request.body = deflated_data
request.headers['Content-Encoding'] = 'deflate'
request.headers['Content-Length'] = str(len(deflated_data))
2016-03-02 12:12:05 +08:00
2019-09-04 00:00:03 +02:00
class HTTPieHTTPSAdapter(HTTPAdapter):
def __init__(self, ssl_version=None, **kwargs):
2016-03-02 12:12:05 +08:00
self._ssl_version = ssl_version
super().__init__(**kwargs)
2016-03-02 12:12:05 +08:00
def init_poolmanager(self, *args, **kwargs):
kwargs['ssl_version'] = self._ssl_version
super().init_poolmanager(*args, **kwargs)
2016-03-02 12:12:05 +08:00
2019-08-29 10:44:59 +02:00
2019-09-01 11:38:14 +02:00
def build_requests_session(
ssl_version: str = None,
2019-09-01 11:38:14 +02:00
) -> requests.Session:
2015-02-05 15:25:00 +01:00
requests_session = requests.Session()
2019-09-01 11:38:14 +02:00
# Install our adapter.
2019-09-04 00:00:03 +02:00
requests_session.mount('https://', HTTPieHTTPSAdapter(
ssl_version=(
SSL_VERSION_ARG_MAPPING[ssl_version]
if ssl_version else None
)
))
2019-09-01 11:38:14 +02:00
# Install adapters from plugins.
for plugin_cls in plugin_manager.get_transport_plugins():
transport_plugin = plugin_cls()
requests_session.mount(
prefix=transport_plugin.prefix,
adapter=transport_plugin.get_adapter(),
)
2015-02-05 15:25:00 +01:00
return requests_session
2019-08-31 17:52:56 +02:00
def dump_request(kwargs: dict):
2019-08-31 18:00:03 +02:00
sys.stderr.write(
f'\n>>> requests.request(**{repr_dict(kwargs)})\n\n')
2019-08-31 17:52:56 +02:00
def finalize_headers(headers: RequestHeadersDict) -> RequestHeadersDict:
final_headers = RequestHeadersDict()
2016-08-13 22:40:01 +02:00
for name, value in headers.items():
if value is not None:
# >leading or trailing LWS MAY be removed without
# >changing the semantics of the field value"
# -https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
# Also, requests raises `InvalidHeader` for leading spaces.
value = value.strip()
if isinstance(value, str):
2017-03-10 11:27:38 +01:00
# See: https://github.com/jakubroztocil/httpie/issues/212
2016-08-13 22:40:01 +02:00
value = value.encode('utf8')
final_headers[name] = value
return final_headers
2019-09-01 11:38:14 +02:00
def make_default_headers(args: argparse.Namespace) -> RequestHeadersDict:
2019-08-31 17:52:56 +02:00
default_headers = RequestHeadersDict({
2012-12-17 17:02:27 +01:00
'User-Agent': DEFAULT_UA
2018-02-22 12:52:57 +01:00
})
auto_json = args.data and not args.form
if args.json or auto_json:
2016-07-02 14:18:36 +02:00
default_headers['Accept'] = JSON_ACCEPT
if args.json or (auto_json and args.data):
2016-07-02 14:18:36 +02:00
default_headers['Content-Type'] = JSON_CONTENT_TYPE
elif args.form and not args.files:
2012-09-07 12:48:59 +02:00
# If sending files, `requests` will set
# the `Content-Type` for us.
2016-07-02 14:18:36 +02:00
default_headers['Content-Type'] = FORM_CONTENT_TYPE
return default_headers
def make_send_kwargs(args: argparse.Namespace) -> dict:
kwargs = {
'timeout': args.timeout or None,
'allow_redirects': False,
}
return kwargs
def make_send_kwargs_mergeable_from_env(args: argparse.Namespace) -> dict:
cert = None
if args.cert:
cert = args.cert
if args.cert_key:
cert = cert, args.cert_key
kwargs = {
'proxies': {p.key: p.value for p in args.proxy},
'stream': True,
'verify': {
'yes': True,
'true': True,
'no': False,
'false': False,
}.get(args.verify.lower(), args.verify),
'cert': cert,
}
return kwargs
def make_request_kwargs(
2019-09-01 21:15:39 +02:00
args: argparse.Namespace,
base_headers: RequestHeadersDict = None
) -> dict:
"""
Translate our `args` into `requests.Request` keyword arguments.
"""
# Serialize JSON data, if needed.
data = args.data
auto_json = data and not args.form
if (args.json or auto_json) and isinstance(data, dict):
if data:
data = json.dumps(data)
else:
# We need to set data to an empty string to prevent requests
# from assigning an empty list to `response.request.data`.
data = ''
# Finalize headers.
2019-09-01 11:38:14 +02:00
headers = make_default_headers(args)
if base_headers:
headers.update(base_headers)
headers.update(args.headers)
2016-08-13 22:40:01 +02:00
headers = finalize_headers(headers)
kwargs = {
'method': args.method.lower(),
'url': args.url,
'headers': headers,
'data': data,
2016-11-23 22:01:58 +01:00
'auth': args.auth,
'params': args.params,
'files': args.files,
}
return kwargs