You've already forked httpie-cli
mirror of
https://github.com/httpie/cli.git
synced 2025-07-15 01:34:27 +02:00
Don't fetch the response body unless needed.
E.g., this will only read the response headers but won't download the whole file: http GET --headers example.org/big-file.avi The request method is respected (i.e., it doesn't switch to HEAD like cURL does).
This commit is contained in:
@ -373,6 +373,7 @@ Changelog
|
|||||||
=========
|
=========
|
||||||
|
|
||||||
* `0.2.7dev`_
|
* `0.2.7dev`_
|
||||||
|
* Response body is fetched only when needed (e.g., not with ``--headers``).
|
||||||
* Updated Solarized color scheme.
|
* Updated Solarized color scheme.
|
||||||
* Windows: Added ``--output FILE`` to store output into a file
|
* Windows: Added ``--output FILE`` to store output into a file
|
||||||
(piping results into corrupted data on Windows).
|
(piping results into corrupted data on Windows).
|
||||||
|
@ -15,7 +15,7 @@ import requests
|
|||||||
import requests.auth
|
import requests.auth
|
||||||
from requests.compat import str
|
from requests.compat import str
|
||||||
|
|
||||||
from .models import HTTPMessage, Environment
|
from .models import HTTPRequest, HTTPResponse, Environment
|
||||||
from .output import OutputProcessor, format
|
from .output import OutputProcessor, format
|
||||||
from .input import (OUT_REQ_BODY, OUT_REQ_HEAD,
|
from .input import (OUT_REQ_BODY, OUT_REQ_HEAD,
|
||||||
OUT_RESP_HEAD, OUT_RESP_BODY)
|
OUT_RESP_HEAD, OUT_RESP_BODY)
|
||||||
@ -102,7 +102,7 @@ def get_output(args, env, request, response):
|
|||||||
if (OUT_REQ_HEAD in args.output_options
|
if (OUT_REQ_HEAD in args.output_options
|
||||||
or OUT_REQ_BODY in args.output_options):
|
or OUT_REQ_BODY in args.output_options):
|
||||||
exchange.append(format(
|
exchange.append(format(
|
||||||
HTTPMessage.from_request(request),
|
msg=HTTPRequest(request),
|
||||||
env=env,
|
env=env,
|
||||||
prettifier=prettifier,
|
prettifier=prettifier,
|
||||||
with_headers=OUT_REQ_HEAD in args.output_options,
|
with_headers=OUT_REQ_HEAD in args.output_options,
|
||||||
@ -112,7 +112,7 @@ def get_output(args, env, request, response):
|
|||||||
if (OUT_RESP_HEAD in args.output_options
|
if (OUT_RESP_HEAD in args.output_options
|
||||||
or OUT_RESP_BODY in args.output_options):
|
or OUT_RESP_BODY in args.output_options):
|
||||||
exchange.append(format(
|
exchange.append(format(
|
||||||
HTTPMessage.from_response(response),
|
msg=HTTPResponse(response),
|
||||||
env=env,
|
env=env,
|
||||||
prettifier=prettifier,
|
prettifier=prettifier,
|
||||||
with_headers=OUT_RESP_HEAD in args.output_options,
|
with_headers=OUT_RESP_HEAD in args.output_options,
|
||||||
|
118
httpie/models.py
118
httpie/models.py
@ -46,100 +46,112 @@ class Environment(object):
|
|||||||
class HTTPMessage(object):
|
class HTTPMessage(object):
|
||||||
"""Model representing an HTTP message."""
|
"""Model representing an HTTP message."""
|
||||||
|
|
||||||
def __init__(self, line, headers, body, encoding=None, content_type=None):
|
def __init__(self, orig):
|
||||||
"""All args are a `str` except for `body` which is a `bytes`."""
|
self._orig = orig
|
||||||
|
|
||||||
assert isinstance(line, str)
|
@property
|
||||||
assert content_type is None or isinstance(content_type, str)
|
def content_type(self):
|
||||||
assert isinstance(body, bytes)
|
return str(self._orig.headers.get('Content-Type', ''))
|
||||||
|
|
||||||
self.line = line # {Request,Status}-Line
|
|
||||||
self.headers = headers
|
|
||||||
self.body = body
|
|
||||||
self.encoding = encoding or 'utf8'
|
|
||||||
self.content_type = content_type
|
|
||||||
|
|
||||||
@classmethod
|
class HTTPResponse(HTTPMessage):
|
||||||
def from_response(cls, response):
|
"""A `requests.models.Response` wrapper."""
|
||||||
"""Make an `HTTPMessage` from `requests.models.Response`."""
|
|
||||||
encoding = response.encoding or None
|
|
||||||
original = response.raw._original_response
|
|
||||||
response_headers = response.headers
|
|
||||||
status_line = str('HTTP/{version} {status} {reason}'.format(
|
|
||||||
version='.'.join(str(original.version)),
|
|
||||||
status=original.status,
|
|
||||||
reason=original.reason
|
|
||||||
))
|
|
||||||
body = response.content
|
|
||||||
|
|
||||||
return cls(line=status_line,
|
@property
|
||||||
headers=str(original.msg),
|
def line(self):
|
||||||
body=body,
|
"""Return Status-Line"""
|
||||||
encoding=encoding,
|
original = self._orig.raw._original_response
|
||||||
content_type=str(response_headers.get('Content-Type', '')))
|
return str('HTTP/{version} {status} {reason}'.format(
|
||||||
|
version='.'.join(str(original.version)),
|
||||||
|
status=original.status,
|
||||||
|
reason=original.reason
|
||||||
|
))
|
||||||
|
|
||||||
@classmethod
|
@property
|
||||||
def from_request(cls, request):
|
def headers(self):
|
||||||
"""Make an `HTTPMessage` from `requests.models.Request`."""
|
return str(self._orig.raw._original_response.msg)
|
||||||
|
|
||||||
url = urlparse(request.url)
|
@property
|
||||||
|
def encoding(self):
|
||||||
|
return self._orig.encoding or 'utf8'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def body(self):
|
||||||
|
# Only now the response body is fetched.
|
||||||
|
# Shouldn't be touched unless the body is actually needed.
|
||||||
|
return self._orig.content
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPRequest(HTTPMessage):
|
||||||
|
"""A `requests.models.Request` wrapper."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def line(self):
|
||||||
|
"""Return Request-Line"""
|
||||||
|
url = urlparse(self._orig.url)
|
||||||
|
|
||||||
# Querystring
|
# Querystring
|
||||||
qs = ''
|
qs = ''
|
||||||
if url.query or request.params:
|
if url.query or self._orig.params:
|
||||||
qs = '?'
|
qs = '?'
|
||||||
if url.query:
|
if url.query:
|
||||||
qs += url.query
|
qs += url.query
|
||||||
# Requests doesn't make params part of ``request.url``.
|
# Requests doesn't make params part of ``request.url``.
|
||||||
if request.params:
|
if self._orig.params:
|
||||||
if url.query:
|
if url.query:
|
||||||
qs += '&'
|
qs += '&'
|
||||||
#noinspection PyUnresolvedReferences
|
#noinspection PyUnresolvedReferences
|
||||||
qs += type(request)._encode_params(request.params)
|
qs += type(self._orig)._encode_params(self._orig.params)
|
||||||
|
|
||||||
# Request-Line
|
# Request-Line
|
||||||
request_line = str('{method} {path}{query} HTTP/1.1'.format(
|
return str('{method} {path}{query} HTTP/1.1'.format(
|
||||||
method=request.method,
|
method=self._orig.method,
|
||||||
path=url.path or '/',
|
path=url.path or '/',
|
||||||
query=qs
|
query=qs
|
||||||
))
|
))
|
||||||
|
|
||||||
# Headers
|
@property
|
||||||
headers = dict(request.headers)
|
def headers(self):
|
||||||
|
headers = dict(self._orig.headers)
|
||||||
content_type = headers.get('Content-Type')
|
content_type = headers.get('Content-Type')
|
||||||
|
|
||||||
if isinstance(content_type, bytes):
|
if isinstance(content_type, bytes):
|
||||||
# Happens when uploading files.
|
# Happens when uploading files.
|
||||||
# TODO: submit a bug report for Requests
|
# TODO: submit a bug report for Requests
|
||||||
content_type = headers['Content-Type'] = content_type.decode('utf8')
|
headers['Content-Type'] = str(content_type)
|
||||||
|
|
||||||
if 'Host' not in headers:
|
if 'Host' not in headers:
|
||||||
headers['Host'] = url.netloc
|
headers['Host'] = urlparse(self._orig.url).netloc
|
||||||
headers = '\n'.join('%s: %s' % (name, value)
|
|
||||||
for name, value in headers.items())
|
|
||||||
|
|
||||||
# Body
|
return '\n'.join('%s: %s' % (name, value)
|
||||||
if request.files:
|
for name, value in headers.items())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def encoding(self):
|
||||||
|
return 'utf8'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def body(self):
|
||||||
|
"""Reconstruct and return the original request body bytes."""
|
||||||
|
if self._orig.files:
|
||||||
# TODO: would be nice if we didn't need to encode the files again
|
# TODO: would be nice if we didn't need to encode the files again
|
||||||
for fn, fd in request.files.values():
|
# FIXME: Also the boundary header doesn't match the one used.
|
||||||
|
for fn, fd in self._orig.files.values():
|
||||||
# Rewind the files as they have already been read before.
|
# Rewind the files as they have already been read before.
|
||||||
fd.seek(0)
|
fd.seek(0)
|
||||||
body, _ = request._encode_files(request.files)
|
body, _ = self._orig._encode_files(self._orig.files)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
body = request.data
|
body = self._orig.data
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# requests < 0.12.1
|
# requests < 0.12.1
|
||||||
body = request._enc_data
|
body = self._orig._enc_data
|
||||||
|
|
||||||
if isinstance(body, dict):
|
if isinstance(body, dict):
|
||||||
#noinspection PyUnresolvedReferences
|
#noinspection PyUnresolvedReferences
|
||||||
body = type(request)._encode_params(body)
|
body = type(self._orig)._encode_params(body)
|
||||||
|
|
||||||
if isinstance(body, str):
|
if isinstance(body, str):
|
||||||
body = body.encode('utf8')
|
body = body.encode('utf8')
|
||||||
|
|
||||||
return cls(line=request_line,
|
return body
|
||||||
headers=headers,
|
|
||||||
body=body,
|
|
||||||
content_type=content_type)
|
|
||||||
|
Reference in New Issue
Block a user