httpie-cli/httpie/models.py

126 lines
3.5 KiB
Python
Raw Normal View History

from typing import Iterable, Optional
2019-08-29 08:53:56 +02:00
from urllib.parse import urlsplit
class HTTPMessage:
"""Abstract class for HTTP messages."""
def __init__(self, orig):
self._orig = orig
def iter_body(self, chunk_size: int) -> Iterable[bytes]:
"""Return an iterator over the body."""
raise NotImplementedError()
def iter_lines(self, chunk_size: int) -> Iterable[bytes]:
"""Return an iterator over the body yielding (`line`, `line_feed`)."""
raise NotImplementedError()
@property
def headers(self) -> str:
"""Return a `str` with the message's headers."""
raise NotImplementedError()
@property
def encoding(self) -> Optional[str]:
"""Return a `str` with the message's encoding, if known."""
raise NotImplementedError()
@property
def body(self) -> bytes:
"""Return a `bytes` with the message's body."""
raise NotImplementedError()
@property
def content_type(self) -> str:
"""Return the message content type."""
2014-04-26 17:16:11 +02:00
ct = self._orig.headers.get('Content-Type', '')
if not isinstance(ct, str):
ct = ct.decode('utf8')
return ct
class HTTPResponse(HTTPMessage):
"""A :class:`requests.models.Response` wrapper."""
def iter_body(self, chunk_size=1):
return self._orig.iter_content(chunk_size=chunk_size)
def iter_lines(self, chunk_size):
2012-08-10 01:07:01 +02:00
return ((line, b'\n') for line in self._orig.iter_lines(chunk_size))
2016-03-01 19:53:23 +01:00
# noinspection PyProtectedMember
@property
def headers(self):
original = self._orig.raw._original_response
version = {
9: '0.9',
10: '1.0',
11: '1.1',
20: '2',
}[original.version]
2019-08-31 15:17:10 +02:00
status_line = f'HTTP/{version} {original.status} {original.reason}'
2012-08-10 01:07:01 +02:00
headers = [status_line]
2021-05-27 13:05:41 +02:00
# `original.msg` is a `http.client.HTTPMessage`
# `_headers` is a 2-tuple
headers.extend(
f'{header[0]}: {header[1]}' for header in original.msg._headers)
2012-08-10 01:07:01 +02:00
return '\r\n'.join(headers)
@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 :class:`requests.models.Request` wrapper."""
def iter_body(self, chunk_size):
yield self.body
def iter_lines(self, chunk_size):
yield self.body, b''
@property
def headers(self):
url = urlsplit(self._orig.url)
request_line = '{method} {path}{query} HTTP/1.1'.format(
method=self._orig.method,
path=url.path or '/',
query=f'?{url.query}' if url.query else ''
)
headers = dict(self._orig.headers)
2014-06-28 13:24:14 +02:00
if 'Host' not in self._orig.headers:
headers['Host'] = url.netloc.split('@')[-1]
headers = [
f'{name}: {value if isinstance(value, str) else value.decode("utf-8")}'
for name, value in headers.items()
]
headers.insert(0, request_line)
headers = '\r\n'.join(headers).strip()
return headers
@property
def encoding(self):
return 'utf8'
@property
def body(self):
2013-01-03 14:54:34 +01:00
body = self._orig.body
if isinstance(body, str):
# Happens with JSON/form request data parsed from the command line.
body = body.encode('utf8')
return body or b''