forked from extern/httpie-cli
Added exit status for timed-out requests.
This commit is contained in:
parent
76feea2f68
commit
c01dd8d64a
12
README.rst
12
README.rst
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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.
|
||||||
|
@ -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:
|
||||||
|
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user