httpie-cli/httpie/client.py

219 lines
6.7 KiB
Python
Raw Normal View History

import json
import sys
import http.client
import requests
2019-08-29 11:46:08 +02:00
from contextlib import contextmanager
from requests.adapters import HTTPAdapter
from requests.structures import CaseInsensitiveDict
from httpie import sessions
from httpie import __version__
from httpie.input import SSL_VERSION_ARG_MAPPING
from httpie.plugins import plugin_manager
2016-03-04 18:42:13 +01:00
from httpie.utils import repr_dict_nice
import zlib
try:
2016-04-28 12:28:20 +02:00
# https://urllib3.readthedocs.io/en/latest/security.html
2017-12-28 18:32:12 +01:00
# noinspection PyPackageRequirements
import urllib3
urllib3.disable_warnings()
2017-12-28 18:32:12 +01:00
except (ImportError, AttributeError):
2016-01-01 22:41:58 +01:00
# In some rare cases, the user may have an old version of the requests
# or urllib3, and there is no method called "disable_warnings." In these
# cases, we don't need to call the method.
# They may get some noisy output but execution shouldn't die. Move on.
pass
FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded; charset=utf-8'
JSON_CONTENT_TYPE = 'application/json'
JSON_ACCEPT = '{0}, */*'.format(JSON_CONTENT_TYPE)
DEFAULT_UA = 'HTTPie/%s' % __version__
# 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
class HTTPieHTTPAdapter(HTTPAdapter):
def __init__(self, ssl_version=None, **kwargs):
self._ssl_version = ssl_version
super().__init__(**kwargs)
def init_poolmanager(self, *args, **kwargs):
kwargs['ssl_version'] = self._ssl_version
super().init_poolmanager(*args, **kwargs)
class ContentCompressionHttpAdapter(HTTPAdapter):
def __init__(self, compress, **kwargs):
self.compress = compress
super().__init__(**kwargs)
def send(self, request, **kwargs):
if request.body and self.compress > 0:
deflater = zlib.compressobj()
if isinstance(request.body, bytes):
deflated_data = deflater.compress(request.body)
else:
deflated_data = deflater.compress(request.body.encode())
deflated_data += deflater.flush()
if len(deflated_data) < len(request.body) or self.compress > 1:
request.body = deflated_data
request.headers['Content-Encoding'] = 'deflate'
request.headers['Content-Length'] = str(len(deflated_data))
return super().send(request, **kwargs)
def get_requests_session(ssl_version, compress):
requests_session = requests.Session()
requests_session.mount(
'https://',
HTTPieHTTPAdapter(ssl_version=ssl_version)
)
if compress:
adapter = ContentCompressionHttpAdapter(compress)
for prefix in ['http://', 'https://']:
requests_session.mount(prefix, adapter)
for cls in plugin_manager.get_transport_plugins():
2015-02-05 15:55:20 +01:00
transport_plugin = cls()
requests_session.mount(prefix=transport_plugin.prefix,
adapter=transport_plugin.get_adapter())
return requests_session
2012-09-17 02:15:00 +02:00
def get_response(args, config_dir):
"""Send the request and return a `request.Response`."""
ssl_version = None
if args.ssl_version:
ssl_version = SSL_VERSION_ARG_MAPPING[args.ssl_version]
requests_session = get_requests_session(ssl_version, args.compress)
2016-02-29 07:21:25 +01:00
requests_session.max_redirects = args.max_redirects
with max_headers(args.max_headers):
if not args.session and not args.session_read_only:
kwargs = get_requests_kwargs(args)
if args.debug:
dump_request(kwargs)
response = requests_session.request(**kwargs)
else:
response = sessions.get_response(
requests_session=requests_session,
args=args,
config_dir=config_dir,
session_name=args.session or args.session_read_only,
read_only=bool(args.session_read_only),
)
return response
def dump_request(kwargs):
2015-02-28 17:02:05 +01:00
sys.stderr.write('\n>>> requests.request(**%s)\n\n'
2016-03-04 18:42:13 +01:00
% repr_dict_nice(kwargs))
2016-08-13 22:40:01 +02:00
def finalize_headers(headers):
final_headers = {}
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
def get_default_headers(args):
default_headers = CaseInsensitiveDict({
'User-Agent': DEFAULT_UA
})
auto_json = args.data and not args.form
if args.json or auto_json:
default_headers['Accept'] = JSON_ACCEPT
if args.json or (auto_json and args.data):
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.
default_headers['Content-Type'] = FORM_CONTENT_TYPE
return default_headers
def get_requests_kwargs(args, base_headers=None):
"""
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.
headers = get_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)
cert = None
if args.cert:
cert = args.cert
2015-01-23 23:54:27 +01:00
if args.cert_key:
cert = cert, args.cert_key
kwargs = {
'stream': True,
'method': args.method.lower(),
'url': args.url,
'headers': headers,
'data': data,
'verify': {
'yes': True,
'true': True,
'no': False,
'false': False,
}.get(args.verify.lower(), args.verify),
'cert': cert,
2019-08-29 10:04:49 +02:00
'timeout': args.timeout or None,
'auth': args.auth,
2017-12-28 18:15:17 +01:00
'proxies': {p.key: p.value for p in args.proxy},
'files': args.files,
2012-09-07 11:58:39 +02:00
'allow_redirects': args.follow,
'params': args.params,
}
return kwargs