Added the option to print the request

It is now possible to print any combination of the following
request-response bits:

    - Request headers (H)
    - Request body (B)
    - Response headers (h)
    - Response body (b)

The output is controlled by the --print / -p option which
defaults to "hb" (i.e., response headers and response body).

Note that -p was previously shortcut for --prety.

Closes #29.
This commit is contained in:
Jakub Roztocil 2012-03-14 00:05:44 +01:00
parent 31c28807c9
commit 02622a4135
6 changed files with 168 additions and 57 deletions

View File

@ -1,4 +1,4 @@
Copyright © 2012 Jakub Roztocil Copyright © 2012 Jakub Roztocil <jakub@roztocil.name>
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:

View File

@ -89,9 +89,11 @@ Flags
Most of the flags mirror the arguments understood by ``requests.request``. See ``http -h`` for more details:: Most of the flags mirror the arguments understood by ``requests.request``. See ``http -h`` for more details::
usage: http [-h] [--version] [--json | --form] [--traceback] usage: http [-h] [--version] [--json | --form] [--traceback]
[--pretty | --ugly] [--headers | --body] [--style STYLE] [--pretty | --ugly]
[--auth AUTH] [--verify VERIFY] [--proxy PROXY] [--print OUTPUT_OPTIONS | --headers | --body]
[--allow-redirects] [--file PATH] [--timeout TIMEOUT] [--style STYLE] [--auth AUTH] [--verify VERIFY]
[--proxy PROXY] [--allow-redirects] [--file PATH]
[--timeout TIMEOUT]
METHOD URL [items [items ...]] METHOD URL [items [items ...]]
HTTPie - cURL for humans. HTTPie - cURL for humans.
@ -113,13 +115,21 @@ Most of the flags mirror the arguments understood by ``requests.request``. See `
Type to application/x-www-form-urlencoded, if not Type to application/x-www-form-urlencoded, if not
specified. specified.
--traceback Print exception traceback should one occur. --traceback Print exception traceback should one occur.
--pretty, -p If stdout is a terminal, the response is prettified by --pretty If stdout is a terminal, the response is prettified by
default (colorized and indented if it is JSON). This default (colorized and indented if it is JSON). This
flag ensures prettifying even when stdout is flag ensures prettifying even when stdout is
redirected. redirected.
--ugly, -u Do not prettify the response. --ugly, -u Do not prettify the response.
--headers, -t Print only the response headers. --print OUTPUT_OPTIONS, -p OUTPUT_OPTIONS
--body, -b Print only the response body. String specifying what should the output contain. "H"
stands for request headers and "B" for request body.
"h" stands for response headers and "b" for response
body. Defaults to "hb" which means that the whole
response (headers and body) is printed.
--headers, -t Print only the response headers. It's a shortcut for
--print=h.
--body, -b Print only the response body. It's a shortcut for
--print=b.
--style STYLE, -s STYLE --style STYLE, -s STYLE
Output coloring style, one of autumn, borland, bw, Output coloring style, one of autumn, borland, bw,
colorful, default, emacs, friendly, fruity, manni, colorful, default, emacs, friendly, fruity, manni,

View File

@ -3,5 +3,5 @@ HTTPie - cURL for humans.
""" """
__author__ = 'Jakub Roztocil' __author__ = 'Jakub Roztocil'
__version__ = '0.1.6' __version__ = '0.1.7-dev'
__licence__ = 'BSD' __licence__ = 'BSD'

View File

@ -2,6 +2,7 @@
import os import os
import sys import sys
import json import json
from urlparse import urlparse
import requests import requests
try: try:
from collections import OrderedDict from collections import OrderedDict
@ -18,6 +19,69 @@ TYPE_FORM = 'application/x-www-form-urlencoded; charset=utf-8'
TYPE_JSON = 'application/json; charset=utf-8' TYPE_JSON = 'application/json; charset=utf-8'
class HTTPMessage(object):
def __init__(self, line, headers, body, content_type=None):
# {Request,Status}-Line
self.line = line
self.headers = headers
self.body = body
self.content_type = content_type
def format_http_message(message, prettifier=None,
with_headers=True, with_body=True):
bits = []
if with_headers:
if prettifier:
bits.append(prettifier.headers(message.line))
bits.append(prettifier.headers(message.headers))
else:
bits.append(message.line)
bits.append(message.headers)
if with_body:
bits.append('\n')
if with_body:
if prettifier and message.content_type:
bits.append(prettifier.body(message.body, message.content_type))
else:
bits.append(message.body)
bits = [bit.strip() for bit in bits]
bits.append('')
return '\n'.join(bits)
def make_request_message(request):
"""Make an `HTTPMessage` from `requests.models.Request`."""
url = urlparse(request.url)
request_headers = dict(request.headers)
request_headers['Host'] = url.netloc
return HTTPMessage(
line='{method} {path} HTTP/1.1'.format(
method=request.method,
path=url.path),
headers='\n'.join('%s: %s' % (name, value)
for name, value
in request_headers.iteritems()),
body=request._enc_data,
content_type=request_headers.get('Content-Type')
)
def make_response_message(response):
"""Make an `HTTPMessage` from `requests.models.Response`."""
encoding = response.encoding or 'ISO-8859-1'
original = response.raw._original_response
response_headers = response.headers
return HTTPMessage(
line='HTTP/{version} {status} {reason}'.format(
version='.'.join(str(original.version)),
status=original.status, reason=original.reason,),
headers=str(original.msg).decode(encoding),
body=response.content.decode(encoding) if response.content else u'',
content_type=response_headers.get('Content-Type'))
def main(args=None, def main(args=None,
stdin=sys.stdin, stdin=sys.stdin,
stdin_isatty=sys.stdin.isatty(), stdin_isatty=sys.stdin.isatty(),
@ -28,7 +92,8 @@ def main(args=None,
args = parser.parse_args(args if args is not None else sys.argv[1:]) args = parser.parse_args(args if args is not None else sys.argv[1:])
do_prettify = (args.prettify is True or do_prettify = (args.prettify is True or
(args.prettify == cli.PRETTIFY_STDOUT_TTY_ONLY and stdout_isatty)) (args.prettify == cli.PRETTIFY_STDOUT_TTY_ONLY
and stdout_isatty))
# Parse request headers and data from the command line. # Parse request headers and data from the command line.
headers = CaseInsensitiveDict() headers = CaseInsensitiveDict()
@ -79,36 +144,31 @@ def main(args=None,
sys.stderr.write(str(e.message) + '\n') sys.stderr.write(str(e.message) + '\n')
sys.exit(1) sys.exit(1)
# Reconstruct the raw response. prettifier = pretty.PrettyHttp(args.style) if do_prettify else None
encoding = response.encoding or 'ISO-8859-1'
original = response.raw._original_response
status_line, headers, body = (
'HTTP/{version} {status} {reason}'.format(
version='.'.join(str(original.version)),
status=original.status, reason=original.reason,
),
str(original.msg).decode(encoding),
response.content.decode(encoding) if response.content else u''
)
if do_prettify: output_request = (cli.OUT_REQUEST_HEADERS in args.output_options
prettify = pretty.PrettyHttp(args.style) or cli.OUT_REQUEST_BODY in args.output_options)
if args.print_headers:
status_line = prettify.headers(status_line)
headers = prettify.headers(headers)
if args.print_body and 'Content-Type' in response.headers:
body = prettify.body(body, response.headers['Content-Type'])
# Output. output_response = (cli.OUT_RESPONSE_HEADERS in args.output_options
# TODO: preserve leading/trailing whitespaces in the body. or cli.OUT_RESPONSE_BODY in args.output_options)
# Some of the Pygments styles add superfluous line breaks.
if args.print_headers: if output_request:
stdout.write(status_line.strip()) stdout.write(format_http_message(
message=make_request_message(response.request),
prettifier=prettifier,
with_headers=cli.OUT_REQUEST_HEADERS in args.output_options,
with_body=cli.OUT_REQUEST_BODY in args.output_options
).encode('utf-8'))
if output_response:
stdout.write('\n') stdout.write('\n')
stdout.write(headers.strip().encode('utf-8'))
stdout.write('\n\n') if output_response:
if args.print_body: stdout.write(format_http_message(
stdout.write(body.strip().encode('utf-8')) message=make_response_message(response),
prettifier=prettifier,
with_headers=cli.OUT_RESPONSE_HEADERS in args.output_options,
with_body=cli.OUT_RESPONSE_BODY in args.output_options
).encode('utf-8'))
stdout.write('\n') stdout.write('\n')

View File

@ -12,6 +12,16 @@ SEP_DATA = '='
SEP_DATA_RAW_JSON = ':=' SEP_DATA_RAW_JSON = ':='
PRETTIFY_STDOUT_TTY_ONLY = object() PRETTIFY_STDOUT_TTY_ONLY = object()
OUT_REQUEST_HEADERS = 'H'
OUT_REQUEST_BODY = 'B'
OUT_RESPONSE_HEADERS = 'h'
OUT_RESPONSE_BODY = 'b'
OUTPUT_OPTIONS = [OUT_REQUEST_HEADERS,
OUT_REQUEST_BODY,
OUT_RESPONSE_HEADERS,
OUT_RESPONSE_BODY]
class ParseError(Exception): class ParseError(Exception):
pass pass
@ -72,7 +82,19 @@ def _(text):
return ' '.join(text.strip().split()) return ' '.join(text.strip().split())
parser = argparse.ArgumentParser(description=doc.strip(),) class HTTPieArgumentParser(argparse.ArgumentParser):
def parse_args(self, args=None, namespace=None):
args = super(HTTPieArgumentParser, self).parse_args(args, namespace)
self._validate_output_options(args)
return args
def _validate_output_options(self, args):
unknown_output_options = set(args.output_options) - set(OUTPUT_OPTIONS)
if unknown_output_options:
self.error('Unknown output options: %s' % ','.join(unknown_output_options))
parser = HTTPieArgumentParser(description=doc.strip(),)
parser.add_argument('--version', action='version', version=version) parser.add_argument('--version', action='version', version=version)
# Content type. # Content type.
@ -96,7 +118,7 @@ group_type.add_argument(
) )
# Output options. # output_options options.
############################################# #############################################
parser.add_argument( parser.add_argument(
@ -108,13 +130,12 @@ parser.add_argument(
prettify = parser.add_mutually_exclusive_group(required=False) prettify = parser.add_mutually_exclusive_group(required=False)
prettify.add_argument( prettify.add_argument(
'--pretty', '-p', dest='prettify', action='store_true', '--pretty', dest='prettify', action='store_true',
default=PRETTIFY_STDOUT_TTY_ONLY, default=PRETTIFY_STDOUT_TTY_ONLY,
help=_(''' help=_('''
If stdout is a terminal, If stdout is a terminal, the response is prettified
the response is prettified by default (colorized and by default (colorized and indented if it is JSON).
indented if it is JSON). This flag ensures This flag ensures prettifying even when stdout is redirected.
prettifying even when stdout is redirected.
''') ''')
) )
prettify.add_argument( prettify.add_argument(
@ -124,21 +145,41 @@ prettify.add_argument(
''') ''')
) )
only = parser.add_mutually_exclusive_group(required=False) output_options = parser.add_mutually_exclusive_group(required=False)
only.add_argument( output_options.add_argument('--print', '-p', dest='output_options',
'--headers', '-t', dest='print_body', default=OUT_RESPONSE_HEADERS + OUT_RESPONSE_BODY,
action='store_false', default=True, help=_('''
help=(''' String specifying what should the output contain.
"{request_headers}" stands for request headers and
"{request_body}" for request body.
"{response_headers}" stands for response headers and
"{response_body}" for response body.
Defaults to "hb" which means that the whole response
(headers and body) is printed.
'''.format(
request_headers=OUT_REQUEST_HEADERS,
request_body=OUT_REQUEST_BODY,
response_headers=OUT_RESPONSE_HEADERS,
response_body=OUT_RESPONSE_BODY,
))
)
output_options.add_argument(
'--headers', '-t', dest='output_options',
action='store_const', const=OUT_RESPONSE_HEADERS,
help=_('''
Print only the response headers. Print only the response headers.
''') It's a shortcut for --print={0}.
'''.format(OUT_RESPONSE_HEADERS))
) )
only.add_argument( output_options.add_argument(
'--body', '-b', dest='print_headers', '--body', '-b', dest='output_options',
action='store_false', default=True, action='store_const', const=OUT_RESPONSE_BODY,
help=(''' help=_('''
Print only the response body. Print only the response body.
''') It's a shortcut for --print={0}.
'''.format(OUT_RESPONSE_BODY))
) )
parser.add_argument( parser.add_argument(
'--style', '-s', dest='style', default='solarized', metavar='STYLE', '--style', '-s', dest='style', default='solarized', metavar='STYLE',
choices=pretty.AVAILABLE_STYLES, choices=pretty.AVAILABLE_STYLES,

View File

@ -8,8 +8,8 @@ from pygments.formatters.terminal256 import Terminal256Formatter
from pygments.formatters.terminal import TerminalFormatter from pygments.formatters.terminal import TerminalFormatter
from pygments.lexer import RegexLexer, bygroups from pygments.lexer import RegexLexer, bygroups
from pygments.styles import get_style_by_name, STYLE_MAP from pygments.styles import get_style_by_name, STYLE_MAP
from . import solarized
from .pygson import JSONLexer from .pygson import JSONLexer
from . import solarized
DEFAULT_STYLE = 'solarized' DEFAULT_STYLE = 'solarized'