From 02622a4135077b2635428fbf6885927b80b55b68 Mon Sep 17 00:00:00 2001 From: Jakub Roztocil Date: Wed, 14 Mar 2012 00:05:44 +0100 Subject: [PATCH] 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. --- LICENSE | 2 +- README.rst | 24 ++++++--- httpie/__init__.py | 2 +- httpie/__main__.py | 118 ++++++++++++++++++++++++++++++++++----------- httpie/cli.py | 77 ++++++++++++++++++++++------- httpie/pretty.py | 2 +- 6 files changed, 168 insertions(+), 57 deletions(-) diff --git a/LICENSE b/LICENSE index 33848901..027da677 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright © 2012 Jakub Roztocil +Copyright © 2012 Jakub Roztocil Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.rst b/README.rst index 40ebc1dc..562d02b5 100644 --- a/README.rst +++ b/README.rst @@ -89,10 +89,12 @@ Flags Most of the flags mirror the arguments understood by ``requests.request``. See ``http -h`` for more details:: usage: http [-h] [--version] [--json | --form] [--traceback] - [--pretty | --ugly] [--headers | --body] [--style STYLE] - [--auth AUTH] [--verify VERIFY] [--proxy PROXY] - [--allow-redirects] [--file PATH] [--timeout TIMEOUT] - METHOD URL [items [items ...]] + [--pretty | --ugly] + [--print OUTPUT_OPTIONS | --headers | --body] + [--style STYLE] [--auth AUTH] [--verify VERIFY] + [--proxy PROXY] [--allow-redirects] [--file PATH] + [--timeout TIMEOUT] + METHOD URL [items [items ...]] 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 specified. --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 flag ensures prettifying even when stdout is redirected. --ugly, -u Do not prettify the response. - --headers, -t Print only the response headers. - --body, -b Print only the response body. + --print OUTPUT_OPTIONS, -p OUTPUT_OPTIONS + 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 Output coloring style, one of autumn, borland, bw, colorful, default, emacs, friendly, fruity, manni, diff --git a/httpie/__init__.py b/httpie/__init__.py index 1bf9b61b..86121835 100644 --- a/httpie/__init__.py +++ b/httpie/__init__.py @@ -3,5 +3,5 @@ HTTPie - cURL for humans. """ __author__ = 'Jakub Roztocil' -__version__ = '0.1.6' +__version__ = '0.1.7-dev' __licence__ = 'BSD' diff --git a/httpie/__main__.py b/httpie/__main__.py index 713bda13..3b5dda13 100644 --- a/httpie/__main__.py +++ b/httpie/__main__.py @@ -2,6 +2,7 @@ import os import sys import json +from urlparse import urlparse import requests try: 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' +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, stdin=sys.stdin, 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:]) 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. headers = CaseInsensitiveDict() @@ -79,36 +144,31 @@ def main(args=None, sys.stderr.write(str(e.message) + '\n') sys.exit(1) - # Reconstruct the raw response. - 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'' - ) + prettifier = pretty.PrettyHttp(args.style) if do_prettify else None - if do_prettify: - prettify = pretty.PrettyHttp(args.style) - 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_request = (cli.OUT_REQUEST_HEADERS in args.output_options + or cli.OUT_REQUEST_BODY in args.output_options) - # Output. - # TODO: preserve leading/trailing whitespaces in the body. - # Some of the Pygments styles add superfluous line breaks. - if args.print_headers: - stdout.write(status_line.strip()) - stdout.write('\n') - stdout.write(headers.strip().encode('utf-8')) - stdout.write('\n\n') - if args.print_body: - stdout.write(body.strip().encode('utf-8')) + output_response = (cli.OUT_RESPONSE_HEADERS in args.output_options + or cli.OUT_RESPONSE_BODY in args.output_options) + + if output_request: + 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') + + if output_response: + stdout.write(format_http_message( + 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') diff --git a/httpie/cli.py b/httpie/cli.py index 36c86407..584cb7c7 100644 --- a/httpie/cli.py +++ b/httpie/cli.py @@ -12,6 +12,16 @@ SEP_DATA = '=' SEP_DATA_RAW_JSON = ':=' 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): pass @@ -72,7 +82,19 @@ def _(text): 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) # Content type. @@ -96,7 +118,7 @@ group_type.add_argument( ) -# Output options. +# output_options options. ############################################# parser.add_argument( @@ -108,13 +130,12 @@ parser.add_argument( prettify = parser.add_mutually_exclusive_group(required=False) prettify.add_argument( - '--pretty', '-p', dest='prettify', action='store_true', + '--pretty', dest='prettify', action='store_true', default=PRETTIFY_STDOUT_TTY_ONLY, help=_(''' - If stdout is a terminal, - the response is prettified by default (colorized and - indented if it is JSON). This flag ensures - prettifying even when stdout is redirected. + If stdout is a terminal, the response is prettified + by default (colorized and indented if it is JSON). + This flag ensures prettifying even when stdout is redirected. ''') ) prettify.add_argument( @@ -124,21 +145,41 @@ prettify.add_argument( ''') ) -only = parser.add_mutually_exclusive_group(required=False) -only.add_argument( - '--headers', '-t', dest='print_body', - action='store_false', default=True, - help=(''' +output_options = parser.add_mutually_exclusive_group(required=False) +output_options.add_argument('--print', '-p', dest='output_options', + default=OUT_RESPONSE_HEADERS + OUT_RESPONSE_BODY, + 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. - ''') + It's a shortcut for --print={0}. + '''.format(OUT_RESPONSE_HEADERS)) ) -only.add_argument( - '--body', '-b', dest='print_headers', - action='store_false', default=True, - help=(''' +output_options.add_argument( + '--body', '-b', dest='output_options', + action='store_const', const=OUT_RESPONSE_BODY, + help=_(''' Print only the response body. - ''') + It's a shortcut for --print={0}. + '''.format(OUT_RESPONSE_BODY)) ) + parser.add_argument( '--style', '-s', dest='style', default='solarized', metavar='STYLE', choices=pretty.AVAILABLE_STYLES, diff --git a/httpie/pretty.py b/httpie/pretty.py index 68e846e0..f63eaff1 100644 --- a/httpie/pretty.py +++ b/httpie/pretty.py @@ -8,8 +8,8 @@ from pygments.formatters.terminal256 import Terminal256Formatter from pygments.formatters.terminal import TerminalFormatter from pygments.lexer import RegexLexer, bygroups from pygments.styles import get_style_by_name, STYLE_MAP -from . import solarized from .pygson import JSONLexer +from . import solarized DEFAULT_STYLE = 'solarized'