Added exit status for timed-out requests.

This commit is contained in:
Jakub Roztocil 2012-08-07 18:22:47 +02:00
parent 76feea2f68
commit c01dd8d64a
6 changed files with 52 additions and 40 deletions

View File

@ -7,7 +7,7 @@ v0.2.8dev
`README for stable version`_ `README for stable version`_
HTTPie is a **command line HTTP client** whose goal is to make CLI interaction 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 ``http`` command that allows for sending arbitrary HTTP requests with a
simple and natural syntax, and displays colorized responses. HTTPie can be used simple and natural syntax, and displays colorized responses. HTTPie can be used
for **testing, debugging**, and generally **interacting** with HTTP servers. 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 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 ``--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 HTTP status is one of ``3xx``, ``4xx``, or ``5xx``. The exit status will
be ``3`` (unless ``--allow-redirects`` is set), ``4``, or ``5``, 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 .. code-block:: bash
#!/bin/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!' echo 'OK!'
else else
case $? in case $? in
2) echo 'Request timed out!' ;;
3) echo 'Unexpected HTTP 3xx Redirection!' ;; 3) echo 'Unexpected HTTP 3xx Redirection!' ;;
4) echo 'HTTP 4xx Client Error!' ;; 4) echo 'HTTP 4xx Client Error!' ;;
5) echo 'HTTP 5xx Server Error!' ;; 5) echo 'HTTP 5xx Server Error!' ;;
@ -933,6 +936,7 @@ Changelog
========= =========
* `0.2.8dev`_ * `0.2.8dev`_
* Added exit status code ``2`` for timed-out requests.
* Added ``--colors`` and ``--format`` in addition to ``--pretty``, to * Added ``--colors`` and ``--format`` in addition to ``--pretty``, to
be able to separate colorizing and formatting. be able to separate colorizing and formatting.
* `0.2.7`_ (2012-08-07) * `0.2.7`_ (2012-08-07)

View File

@ -10,7 +10,9 @@ __licence__ = 'BSD'
class EXIT: class EXIT:
OK = 0 OK = 0
ERROR = 1 ERROR = 1
# Used only when requested: ERROR_TIMEOUT = 2
# Used only when requested with --check-status:
ERROR_HTTP_3XX = 3 ERROR_HTTP_3XX = 3
ERROR_HTTP_4XX = 4 ERROR_HTTP_4XX = 4
ERROR_HTTP_5XX = 5 ERROR_HTTP_5XX = 5

View File

@ -11,13 +11,11 @@ from . import __doc__
from . import __version__ from . import __version__
from .output import AVAILABLE_STYLES, DEFAULT_STYLE from .output import AVAILABLE_STYLES, DEFAULT_STYLE
from .input import (Parser, AuthCredentialsArgType, KeyValueArgType, from .input import (Parser, AuthCredentialsArgType, KeyValueArgType,
PRETTY_STDOUT_TTY_ONLY,
SEP_PROXY, SEP_CREDENTIALS, SEP_GROUP_ITEMS, SEP_PROXY, SEP_CREDENTIALS, SEP_GROUP_ITEMS,
OUT_REQ_HEAD, OUT_REQ_BODY, OUT_RESP_HEAD, OUT_REQ_HEAD, OUT_REQ_BODY, OUT_RESP_HEAD,
OUT_RESP_BODY, OUTPUT_OPTIONS, OUT_RESP_BODY, OUTPUT_OPTIONS,
PRETTY_STDOUT_TTY_ONLY, PRETTY_ALL, PRETTY_STDOUT_TTY_ONLY, PRETTY_ALL,
PRETTY_FORMAT, PRETTY_FORMAT, PRETTY_COLORS)
PRETTY_COLORS)
def _(text): def _(text):
@ -49,7 +47,7 @@ group_type.add_argument(
The Content-Type is set to application/x-www-form-urlencoded The Content-Type is set to application/x-www-form-urlencoded
(if not specified). (if not specified).
The presence of any file fields results 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. # Output options.
############################################# #############################################
parser.add_argument( parser.add_argument(
'--output', '-o', type=argparse.FileType('w+b'), '--output', '-o', type=argparse.FileType('w+b'),
metavar='FILE', metavar='FILE',
@ -65,7 +62,7 @@ parser.add_argument(
''' '''
Save output to FILE. Save output to FILE.
This option is a replacement for piping 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. being saved.
''' '''
@ -151,8 +148,8 @@ parser.add_argument(
''') % (', '.join(sorted(AVAILABLE_STYLES)), DEFAULT_STYLE) ''') % (', '.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'. Always stream the output by line, i.e., behave like `tail -f'.
Without --stream and with --pretty (either set or implied), Without --stream and with --pretty (either set or implied),
@ -187,7 +184,7 @@ parser.add_argument(
# ``requests.request`` keyword arguments. # ``requests.request`` keyword arguments.
parser.add_argument( parser.add_argument(
'--auth', '-a', '--auth', '-a', metavar='USER:PASS',
type=AuthCredentialsArgType(SEP_CREDENTIALS), type=AuthCredentialsArgType(SEP_CREDENTIALS),
help=_(''' help=_('''
username:password. username:password.
@ -230,10 +227,10 @@ parser.add_argument(
''') ''')
) )
parser.add_argument( parser.add_argument(
'--timeout', type=float, default=30, '--timeout', type=float, default=30, metavar='SECONDS',
help=_(''' help=_('''
The timeout of the request in seconds. The default value is 30 The connection timeout of the request in seconds.
seconds. The default value is 30 seconds.
''') ''')
) )
parser.add_argument( parser.add_argument(

View File

@ -10,6 +10,7 @@ Invocation flow:
5. Exit. 5. Exit.
""" """
from _socket import gaierror
import sys import sys
import json import json
import errno import errno
@ -143,7 +144,7 @@ def main(args=sys.argv[1:], env=Environment()):
except IOError as e: except IOError as e:
if not traceback and e.errno == errno.EPIPE: if not traceback and e.errno == errno.EPIPE:
# Ignore broken pipes unless --debug. # Ignore broken pipes unless --traceback.
env.stderr.write('\n') env.stderr.write('\n')
else: else:
raise raise
@ -153,7 +154,9 @@ def main(args=sys.argv[1:], env=Environment()):
raise raise
env.stderr.write('\n') env.stderr.write('\n')
status = EXIT.ERROR status = EXIT.ERROR
except requests.Timeout:
status = EXIT.ERROR_TIMEOUT
error('Request timed out (%ss).', args.timeout)
except Exception as e: except Exception as e:
# TODO: distinguish between expected and unexpected errors. # TODO: distinguish between expected and unexpected errors.
# network errors vs. bugs, etc. # network errors vs. bugs, etc.

View File

@ -20,10 +20,9 @@ from .input import (OUT_REQ_BODY, OUT_REQ_HEAD,
OUT_RESP_HEAD, OUT_RESP_BODY) OUT_RESP_HEAD, OUT_RESP_BODY)
# Colors on Windows via colorama aren't that great and fruity # Colors on Windows via colorama don't look that
# seems to give the best result there. # great and fruity seems to give the best result there.
DEFAULT_STYLE = 'solarized' if not is_windows else 'fruity' DEFAULT_STYLE = 'solarized' if not is_windows else 'fruity'
#noinspection PySetFunctionToLiteral #noinspection PySetFunctionToLiteral
AVAILABLE_STYLES = set([DEFAULT_STYLE]) | set(STYLE_MAP.keys()) AVAILABLE_STYLES = set([DEFAULT_STYLE]) | set(STYLE_MAP.keys())
@ -335,10 +334,9 @@ class BaseProcessor(object):
def __init__(self, env, **kwargs): def __init__(self, env, **kwargs):
""" """
:param env: :param env: an class:`Environment` instance
an class:`Environment` instance :param kwargs: additional keyword argument that some
:param kwargs: processor might require.
additional keyword argument that some processor might require.
""" """
self.env = env self.env = env
@ -347,8 +345,7 @@ class BaseProcessor(object):
def process_headers(self, headers): def process_headers(self, headers):
"""Return processed `headers` """Return processed `headers`
:param headers: :param headers: The headers as text.
The headers as text.
""" """
return headers return headers
@ -356,14 +353,9 @@ class BaseProcessor(object):
def process_body(self, content, content_type, subtype): def process_body(self, content, content_type, subtype):
"""Return processed `content`. """Return processed `content`.
:param content: :param content: The body content as text
The body content as text :param content_type: Full content type, e.g., 'application/atom+xml'.
:param subtype: E.g. 'xml'.
:param content_type:
Full content type, e.g., 'application/atom+xml'.
:param subtype:
E.g. 'xml'.
""" """
return content return content
@ -458,13 +450,18 @@ class OutputProcessor(object):
} }
def __init__(self, env, groups, **kwargs): 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 group in groups:
for cls in self.installed_processors[group]: for cls in self.installed_processors[group]:
processors.append(cls(env, **kwargs)) processor = cls(env, **kwargs)
if processor.enable:
self.processors = [p for p in processors if p.enabled] self.processors.append(processor)
def process_headers(self, headers): def process_headers(self, headers):
for processor in self.processors: for processor in self.processors:

View File

@ -860,6 +860,15 @@ class ExitStatusTest(BaseTestCase):
) )
self.assertIn('HTTP/1.1 500', r) self.assertIn('HTTP/1.1 500', r)
self.assertEqual(r.exit_status, EXIT.OK) 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): def test_3xx_check_status_exits_3_and_stderr_when_stdout_redirected(self):
r = http( r = http(