2012-07-26 06:37:03 +02:00
|
|
|
"""This module provides the main functionality of HTTPie.
|
|
|
|
|
|
|
|
Invocation flow:
|
|
|
|
|
2014-04-27 00:07:13 +02:00
|
|
|
1. Read, validate and process the input (args, `stdin`).
|
|
|
|
2. Create and send a request.
|
|
|
|
3. Stream, and possibly process and format, the parts
|
|
|
|
of the request-response exchange selected by output options.
|
|
|
|
4. Simultaneously write to `stdout`
|
|
|
|
5. Exit.
|
2012-07-26 06:37:03 +02:00
|
|
|
|
|
|
|
"""
|
2012-07-21 02:59:43 +02:00
|
|
|
import sys
|
2012-08-03 01:01:15 +02:00
|
|
|
import errno
|
2012-07-26 00:26:23 +02:00
|
|
|
|
2012-07-21 02:59:43 +02:00
|
|
|
import requests
|
2012-08-07 14:50:51 +02:00
|
|
|
from requests import __version__ as requests_version
|
|
|
|
from pygments import __version__ as pygments_version
|
2012-07-26 00:26:23 +02:00
|
|
|
|
2014-04-27 00:07:13 +02:00
|
|
|
from httpie import __version__ as httpie_version, ExitStatus
|
|
|
|
from httpie.compat import str, bytes, is_py3
|
|
|
|
from httpie.client import get_response
|
|
|
|
from httpie.downloads import Download
|
|
|
|
from httpie.context import Environment
|
|
|
|
from httpie.plugins import plugin_manager
|
|
|
|
from httpie.output.streams import (
|
|
|
|
build_output_stream,
|
|
|
|
write, write_with_colors_win_py3
|
|
|
|
)
|
2012-07-21 02:59:43 +02:00
|
|
|
|
|
|
|
|
2012-12-11 12:54:34 +01:00
|
|
|
def get_exit_status(http_status, follow=False):
|
2012-12-05 05:03:18 +01:00
|
|
|
"""Translate HTTP status code to exit status code."""
|
2012-12-11 12:54:34 +01:00
|
|
|
if 300 <= http_status <= 399 and not follow:
|
2012-07-23 19:35:44 +02:00
|
|
|
# Redirect
|
2012-12-05 05:03:18 +01:00
|
|
|
return ExitStatus.ERROR_HTTP_3XX
|
2012-12-11 12:54:34 +01:00
|
|
|
elif 400 <= http_status <= 499:
|
2012-07-23 19:35:44 +02:00
|
|
|
# Client Error
|
2012-12-05 05:03:18 +01:00
|
|
|
return ExitStatus.ERROR_HTTP_4XX
|
2012-12-11 12:54:34 +01:00
|
|
|
elif 500 <= http_status <= 599:
|
2012-07-23 19:35:44 +02:00
|
|
|
# Server Error
|
2012-12-05 05:03:18 +01:00
|
|
|
return ExitStatus.ERROR_HTTP_5XX
|
2012-07-23 19:35:44 +02:00
|
|
|
else:
|
2012-12-05 05:03:18 +01:00
|
|
|
return ExitStatus.OK
|
2012-07-23 19:35:44 +02:00
|
|
|
|
|
|
|
|
2012-09-17 00:37:36 +02:00
|
|
|
def print_debug_info(env):
|
2014-04-24 18:32:15 +02:00
|
|
|
env.stderr.writelines([
|
2012-08-18 04:37:22 +02:00
|
|
|
'HTTPie %s\n' % httpie_version,
|
2012-09-17 00:37:36 +02:00
|
|
|
'HTTPie data: %s\n' % env.config.directory,
|
2012-08-18 04:37:22 +02:00
|
|
|
'Requests %s\n' % requests_version,
|
|
|
|
'Pygments %s\n' % pygments_version,
|
|
|
|
'Python %s %s\n' % (sys.version, sys.platform)
|
|
|
|
])
|
|
|
|
|
|
|
|
|
2014-04-26 15:06:51 +02:00
|
|
|
def decode_args(args, stdin_encoding):
|
|
|
|
"""
|
|
|
|
Convert all bytes ags to str
|
|
|
|
by decoding them using stdin encoding.
|
|
|
|
|
|
|
|
"""
|
|
|
|
return [
|
2014-06-03 19:44:22 +02:00
|
|
|
arg.decode(stdin_encoding)
|
|
|
|
if type(arg) == bytes else arg
|
2014-04-26 15:06:51 +02:00
|
|
|
for arg in args
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2015-02-08 17:17:35 +01:00
|
|
|
def main(args=sys.argv[1:], env=Environment(), error=None):
|
2012-07-26 06:37:03 +02:00
|
|
|
"""Run the main program and write the output to ``env.stdout``.
|
2012-07-23 19:35:44 +02:00
|
|
|
|
2012-12-05 05:03:18 +01:00
|
|
|
Return exit status code.
|
2012-07-23 19:35:44 +02:00
|
|
|
|
|
|
|
"""
|
2014-06-03 19:44:22 +02:00
|
|
|
args = decode_args(args, env.stdin_encoding)
|
2013-09-21 23:46:15 +02:00
|
|
|
plugin_manager.load_installed_plugins()
|
|
|
|
|
2014-06-03 19:44:22 +02:00
|
|
|
from httpie.cli import parser
|
|
|
|
|
2012-09-17 02:15:00 +02:00
|
|
|
if env.config.default_options:
|
|
|
|
args = env.config.default_options + args
|
2012-07-30 10:58:16 +02:00
|
|
|
|
2015-02-08 17:17:35 +01:00
|
|
|
def _error(msg, *args, **kwargs):
|
2012-08-04 19:12:51 +02:00
|
|
|
msg = msg % args
|
2012-12-05 05:27:11 +01:00
|
|
|
level = kwargs.get('level', 'error')
|
2013-04-11 21:23:15 +02:00
|
|
|
env.stderr.write('\nhttp: %s: %s\n' % (level, msg))
|
2012-08-04 19:12:51 +02:00
|
|
|
|
2015-02-08 17:17:35 +01:00
|
|
|
if error is None:
|
|
|
|
error = _error
|
|
|
|
|
2012-08-04 19:12:51 +02:00
|
|
|
debug = '--debug' in args
|
2012-08-07 14:50:51 +02:00
|
|
|
traceback = debug or '--traceback' in args
|
2012-12-11 12:54:34 +01:00
|
|
|
exit_status = ExitStatus.OK
|
2012-07-30 10:58:16 +02:00
|
|
|
|
2012-08-07 14:50:51 +02:00
|
|
|
if debug:
|
2012-09-17 00:37:36 +02:00
|
|
|
print_debug_info(env)
|
2012-08-18 04:37:22 +02:00
|
|
|
if args == ['--debug']:
|
2012-12-11 12:54:34 +01:00
|
|
|
return exit_status
|
2012-08-07 14:50:51 +02:00
|
|
|
|
2013-04-12 14:08:19 +02:00
|
|
|
download = None
|
2012-07-30 10:58:16 +02:00
|
|
|
try:
|
2012-08-01 23:21:52 +02:00
|
|
|
args = parser.parse_args(args=args, env=env)
|
2012-09-17 02:15:00 +02:00
|
|
|
|
2013-02-26 15:12:33 +01:00
|
|
|
if args.download:
|
2013-03-24 15:23:18 +01:00
|
|
|
args.follow = True # --download implies --follow.
|
2013-02-26 15:12:33 +01:00
|
|
|
download = Download(
|
|
|
|
output_file=args.output_file,
|
|
|
|
progress_file=env.stderr,
|
|
|
|
resume=args.download_resume
|
|
|
|
)
|
2013-03-24 15:23:18 +01:00
|
|
|
download.pre_request(args.headers)
|
2013-02-26 15:12:33 +01:00
|
|
|
|
2016-02-29 05:56:40 +01:00
|
|
|
last_response = get_response(args, config_dir=env.config.directory)
|
2012-08-01 23:21:52 +02:00
|
|
|
|
2016-02-29 05:56:40 +01:00
|
|
|
redirect_chain = last_response.history + [last_response]
|
|
|
|
for response in redirect_chain:
|
2013-02-26 15:12:33 +01:00
|
|
|
|
2016-02-29 05:56:40 +01:00
|
|
|
if exit_status != ExitStatus.OK:
|
|
|
|
break
|
2013-02-26 15:12:33 +01:00
|
|
|
|
2016-02-29 05:56:40 +01:00
|
|
|
if args.check_status or download:
|
2013-02-26 15:12:33 +01:00
|
|
|
|
2016-02-29 05:56:40 +01:00
|
|
|
exit_status = get_exit_status(
|
|
|
|
http_status=response.status_code,
|
|
|
|
follow=args.follow
|
|
|
|
)
|
2013-02-26 15:12:33 +01:00
|
|
|
|
2016-02-29 05:56:40 +01:00
|
|
|
if not env.stdout_isatty and exit_status != ExitStatus.OK:
|
|
|
|
error('HTTP %s %s',
|
|
|
|
response.raw.status,
|
|
|
|
response.raw.reason,
|
|
|
|
level='warning')
|
|
|
|
|
|
|
|
write_kwargs = {
|
|
|
|
'stream': build_output_stream(args, env,
|
|
|
|
response.request,
|
|
|
|
response),
|
|
|
|
# This will in fact be `stderr` with `--download`
|
|
|
|
'outfile': env.stdout,
|
|
|
|
'flush': env.stdout_isatty or args.stream
|
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
|
|
if env.is_windows and is_py3 and 'colors' in args.prettify:
|
|
|
|
write_with_colors_win_py3(**write_kwargs)
|
|
|
|
else:
|
|
|
|
write(**write_kwargs)
|
|
|
|
except IOError as e:
|
|
|
|
if not traceback and e.errno == errno.EPIPE:
|
|
|
|
# Ignore broken pipes unless --traceback.
|
|
|
|
env.stderr.write('\n')
|
|
|
|
else:
|
|
|
|
raise
|
|
|
|
|
|
|
|
if download and exit_status == ExitStatus.OK:
|
|
|
|
# Last response body download.
|
|
|
|
download_stream, download_to = download.start(last_response)
|
|
|
|
write(
|
|
|
|
stream=download_stream,
|
|
|
|
outfile=download_to,
|
|
|
|
flush=False,
|
|
|
|
)
|
|
|
|
download.finish()
|
|
|
|
if download.interrupted:
|
|
|
|
exit_status = ExitStatus.ERROR
|
|
|
|
error('Incomplete download: size=%d; downloaded=%d' % (
|
|
|
|
download.status.total_size,
|
|
|
|
download.status.downloaded
|
|
|
|
))
|
2012-08-03 01:01:15 +02:00
|
|
|
|
2015-01-19 15:39:46 +01:00
|
|
|
except KeyboardInterrupt:
|
2012-08-07 14:50:51 +02:00
|
|
|
if traceback:
|
2012-08-03 01:01:15 +02:00
|
|
|
raise
|
2012-08-01 23:21:52 +02:00
|
|
|
env.stderr.write('\n')
|
2012-12-11 12:54:34 +01:00
|
|
|
exit_status = ExitStatus.ERROR
|
2015-01-19 15:39:46 +01:00
|
|
|
except SystemExit as e:
|
|
|
|
if e.code != ExitStatus.OK:
|
|
|
|
if traceback:
|
|
|
|
raise
|
|
|
|
env.stderr.write('\n')
|
|
|
|
exit_status = ExitStatus.ERROR
|
2012-08-07 18:22:47 +02:00
|
|
|
except requests.Timeout:
|
2012-12-11 12:54:34 +01:00
|
|
|
exit_status = ExitStatus.ERROR_TIMEOUT
|
2012-08-07 18:22:47 +02:00
|
|
|
error('Request timed out (%ss).', args.timeout)
|
2012-12-05 05:03:18 +01:00
|
|
|
|
2012-08-01 23:21:52 +02:00
|
|
|
except Exception as e:
|
2012-12-05 05:03:18 +01:00
|
|
|
# TODO: Better distinction between expected and unexpected errors.
|
|
|
|
# Network errors vs. bugs, etc.
|
2012-08-07 14:50:51 +02:00
|
|
|
if traceback:
|
2012-08-01 23:21:52 +02:00
|
|
|
raise
|
2015-02-04 19:34:43 +01:00
|
|
|
msg = str(e)
|
|
|
|
if hasattr(e, 'request'):
|
|
|
|
request = e.request
|
|
|
|
if hasattr(request, 'url'):
|
|
|
|
msg += ' while doing %s request to URL: %s' % (
|
|
|
|
request.method, request.url)
|
|
|
|
error('%s: %s', type(e).__name__, msg)
|
2012-12-11 12:54:34 +01:00
|
|
|
exit_status = ExitStatus.ERROR
|
2012-07-24 01:09:14 +02:00
|
|
|
|
2013-04-16 09:55:45 +02:00
|
|
|
finally:
|
|
|
|
if download and not download.finished:
|
|
|
|
download.failed()
|
|
|
|
|
2012-12-11 12:54:34 +01:00
|
|
|
return exit_status
|