From c01dd8d64aaeece2496eb27fe5c907f9e950dfac Mon Sep 17 00:00:00 2001 From: Jakub Roztocil Date: Tue, 7 Aug 2012 18:22:47 +0200 Subject: [PATCH] Added exit status for timed-out requests. --- README.rst | 12 ++++++++---- httpie/__init__.py | 4 +++- httpie/cli.py | 21 +++++++++------------ httpie/core.py | 7 +++++-- httpie/output.py | 39 ++++++++++++++++++--------------------- tests/tests.py | 9 +++++++++ 6 files changed, 52 insertions(+), 40 deletions(-) diff --git a/README.rst b/README.rst index 7f1b97e6..da3807c6 100644 --- a/README.rst +++ b/README.rst @@ -7,7 +7,7 @@ v0.2.8dev ☞ `README for stable version`_ HTTPie is a **command line HTTP client** whose goal is to make CLI interaction -with HTTP-based services as **human-friendly** as possible. It provides a +with web services as **human-friendly** as possible. It provides a simple ``http`` command that allows for sending arbitrary HTTP requests with a simple and natural syntax, and displays colorized responses. HTTPie can be used for **testing, debugging**, and generally **interacting** with HTTP servers. @@ -82,7 +82,8 @@ Or, you can install the **development version** directly from GitHub: There are also packages available for `Ubuntu`_, `Debian`_, and possibly other -Linux distributions as well. +Linux distributions as well. However, they may be a significant delay between +releases and package updates. ===== @@ -809,16 +810,18 @@ When using HTTPie from **shell scripts**, it can be handy to set the ``--check-status`` flag. It instructs HTTPie to exit with an error if the HTTP status is one of ``3xx``, ``4xx``, or ``5xx``. The exit status will be ``3`` (unless ``--allow-redirects`` is set), ``4``, or ``5``, -respectively: +respectively. Also, the ``--timeout`` option allows to overwrite the default +30s timeout: .. code-block:: bash #!/bin/bash - if http --check-status HEAD example.org/health &> /dev/null; then + if http --timeout=2.5 --check-status HEAD example.org/health &> /dev/null; then echo 'OK!' else case $? in + 2) echo 'Request timed out!' ;; 3) echo 'Unexpected HTTP 3xx Redirection!' ;; 4) echo 'HTTP 4xx Client Error!' ;; 5) echo 'HTTP 5xx Server Error!' ;; @@ -933,6 +936,7 @@ Changelog ========= * `0.2.8dev`_ + * Added exit status code ``2`` for timed-out requests. * Added ``--colors`` and ``--format`` in addition to ``--pretty``, to be able to separate colorizing and formatting. * `0.2.7`_ (2012-08-07) diff --git a/httpie/__init__.py b/httpie/__init__.py index c536d641..02688502 100644 --- a/httpie/__init__.py +++ b/httpie/__init__.py @@ -10,7 +10,9 @@ __licence__ = 'BSD' class EXIT: OK = 0 ERROR = 1 - # Used only when requested: + ERROR_TIMEOUT = 2 + + # Used only when requested with --check-status: ERROR_HTTP_3XX = 3 ERROR_HTTP_4XX = 4 ERROR_HTTP_5XX = 5 diff --git a/httpie/cli.py b/httpie/cli.py index 5b3cbf40..3f84babe 100644 --- a/httpie/cli.py +++ b/httpie/cli.py @@ -11,13 +11,11 @@ from . import __doc__ from . import __version__ from .output import AVAILABLE_STYLES, DEFAULT_STYLE from .input import (Parser, AuthCredentialsArgType, KeyValueArgType, - PRETTY_STDOUT_TTY_ONLY, SEP_PROXY, SEP_CREDENTIALS, SEP_GROUP_ITEMS, OUT_REQ_HEAD, OUT_REQ_BODY, OUT_RESP_HEAD, OUT_RESP_BODY, OUTPUT_OPTIONS, PRETTY_STDOUT_TTY_ONLY, PRETTY_ALL, - PRETTY_FORMAT, - PRETTY_COLORS) + PRETTY_FORMAT, PRETTY_COLORS) def _(text): @@ -49,7 +47,7 @@ group_type.add_argument( The Content-Type is set to application/x-www-form-urlencoded (if not specified). The presence of any file fields results - into a multipart/form-data request. + in a multipart/form-data request. ''') ) @@ -57,7 +55,6 @@ group_type.add_argument( # Output options. ############################################# - parser.add_argument( '--output', '-o', type=argparse.FileType('w+b'), metavar='FILE', @@ -65,7 +62,7 @@ parser.add_argument( ''' Save output to FILE. This option is a replacement for piping output to FILE, - which would on Windows result into corrupted data + which would on Windows result in corrupted data being saved. ''' @@ -151,8 +148,8 @@ parser.add_argument( ''') % (', '.join(sorted(AVAILABLE_STYLES)), DEFAULT_STYLE) ) -parser.add_argument('--stream', '-S', action='store_true', default=False, help=_( - ''' +parser.add_argument('--stream', '-S', action='store_true', default=False, + help=_(''' Always stream the output by line, i.e., behave like `tail -f'. Without --stream and with --pretty (either set or implied), @@ -187,7 +184,7 @@ parser.add_argument( # ``requests.request`` keyword arguments. parser.add_argument( - '--auth', '-a', + '--auth', '-a', metavar='USER:PASS', type=AuthCredentialsArgType(SEP_CREDENTIALS), help=_(''' username:password. @@ -230,10 +227,10 @@ parser.add_argument( ''') ) parser.add_argument( - '--timeout', type=float, default=30, + '--timeout', type=float, default=30, metavar='SECONDS', help=_(''' - The timeout of the request in seconds. The default value is 30 - seconds. + The connection timeout of the request in seconds. + The default value is 30 seconds. ''') ) parser.add_argument( diff --git a/httpie/core.py b/httpie/core.py index f8ceb74b..71b730e8 100644 --- a/httpie/core.py +++ b/httpie/core.py @@ -10,6 +10,7 @@ Invocation flow: 5. Exit. """ +from _socket import gaierror import sys import json import errno @@ -143,7 +144,7 @@ def main(args=sys.argv[1:], env=Environment()): except IOError as e: if not traceback and e.errno == errno.EPIPE: - # Ignore broken pipes unless --debug. + # Ignore broken pipes unless --traceback. env.stderr.write('\n') else: raise @@ -153,7 +154,9 @@ def main(args=sys.argv[1:], env=Environment()): raise env.stderr.write('\n') status = EXIT.ERROR - + except requests.Timeout: + status = EXIT.ERROR_TIMEOUT + error('Request timed out (%ss).', args.timeout) except Exception as e: # TODO: distinguish between expected and unexpected errors. # network errors vs. bugs, etc. diff --git a/httpie/output.py b/httpie/output.py index 04b4be5f..de608596 100644 --- a/httpie/output.py +++ b/httpie/output.py @@ -20,10 +20,9 @@ from .input import (OUT_REQ_BODY, OUT_REQ_HEAD, OUT_RESP_HEAD, OUT_RESP_BODY) -# Colors on Windows via colorama aren't that great and fruity -# seems to give the best result there. +# Colors on Windows via colorama don't look that +# great and fruity seems to give the best result there. DEFAULT_STYLE = 'solarized' if not is_windows else 'fruity' - #noinspection PySetFunctionToLiteral AVAILABLE_STYLES = set([DEFAULT_STYLE]) | set(STYLE_MAP.keys()) @@ -335,10 +334,9 @@ class BaseProcessor(object): def __init__(self, env, **kwargs): """ - :param env: - an class:`Environment` instance - :param kwargs: - additional keyword argument that some processor might require. + :param env: an class:`Environment` instance + :param kwargs: additional keyword argument that some + processor might require. """ self.env = env @@ -347,8 +345,7 @@ class BaseProcessor(object): def process_headers(self, headers): """Return processed `headers` - :param headers: - The headers as text. + :param headers: The headers as text. """ return headers @@ -356,14 +353,9 @@ class BaseProcessor(object): def process_body(self, content, content_type, subtype): """Return processed `content`. - :param content: - The body content as text - - :param content_type: - Full content type, e.g., 'application/atom+xml'. - - :param subtype: - E.g. 'xml'. + :param content: The body content as text + :param content_type: Full content type, e.g., 'application/atom+xml'. + :param subtype: E.g. 'xml'. """ return content @@ -458,13 +450,18 @@ class OutputProcessor(object): } def __init__(self, env, groups, **kwargs): + """ + :param env: a :class:`models.Environment` instance + :param groups: the groups of processors to be applied + :param kwargs: additional keyword arguments for processors - processors = [] + """ + self.processors = [] for group in groups: for cls in self.installed_processors[group]: - processors.append(cls(env, **kwargs)) - - self.processors = [p for p in processors if p.enabled] + processor = cls(env, **kwargs) + if processor.enable: + self.processors.append(processor) def process_headers(self, headers): for processor in self.processors: diff --git a/tests/tests.py b/tests/tests.py index 762b9a81..8c34cc67 100755 --- a/tests/tests.py +++ b/tests/tests.py @@ -860,6 +860,15 @@ class ExitStatusTest(BaseTestCase): ) self.assertIn('HTTP/1.1 500', r) self.assertEqual(r.exit_status, EXIT.OK) + self.assertTrue(not r.stderr) + + def test_timeout_exit_status(self): + r = http( + '--timeout=0.5', + 'GET', + httpbin('/delay/1') + ) + self.assertEqual(r.exit_status, EXIT.ERROR_TIMEOUT) def test_3xx_check_status_exits_3_and_stderr_when_stdout_redirected(self): r = http(