Skip to content

Commit 46f030c

Browse files
author
Ahmed TAHRI
committed
⚗️ Try compatible fork Niquests to supercharge HTTPie
1 parent e52a60e commit 46f030c

35 files changed

+218
-106
lines changed

httpie/adapters.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from httpie.cli.dicts import HTTPHeadersDict
2-
from requests.adapters import HTTPAdapter
2+
from niquests.adapters import HTTPAdapter
33

44

55
class HTTPieHTTPAdapter(HTTPAdapter):

httpie/cli/argparser.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from textwrap import dedent
88
from urllib.parse import urlsplit
99

10-
from requests.utils import get_netrc_auth
10+
from niquests.utils import get_netrc_auth
1111

1212
from .argtypes import (
1313
AuthCredentials, SSLCredentials, KeyValueArgType,

httpie/cli/definition.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@
193193
content_types.add_argument(
194194
'--boundary',
195195
short_help=(
196-
'Specify a custom boundary string for multipart/form-data requests. '
196+
'Specify a custom boundary string for multipart/form-data niquests. '
197197
'Only has effect only together with --form.'
198198
)
199199
)
@@ -594,7 +594,7 @@ def format_style_help(available_styles, *, isolation_mode: bool = False):
594594
help="""
595595
Create, or reuse and update a session. Within a session, custom headers,
596596
auth credential, as well as any cookies sent by the server persist between
597-
requests.
597+
niquests.
598598
599599
Session files are stored in:
600600

httpie/cli/requestitems.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def __init__(self, request_type: Optional[RequestType] = None):
3030
self.data = RequestJSONDataDict() if self.is_json else RequestDataDict()
3131
self.files = RequestFilesDict()
3232
self.params = RequestQueryParamsDict()
33-
# To preserve the order of fields in file upload multipart requests.
33+
# To preserve the order of fields in file upload multipart niquests.
3434
self.multipart_data = MultipartRequestDataDict()
3535

3636
@classmethod

httpie/client.py

+18-12
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from typing import Any, Dict, Callable, Iterable
88
from urllib.parse import urlparse, urlunparse
99

10-
import requests
10+
import niquests
1111
# noinspection PyPackageRequirements
1212
import urllib3
1313
from urllib3.util import SKIP_HEADER, SKIPPABLE_HEADERS
@@ -44,6 +44,7 @@ def collect_messages(
4444
env: Environment,
4545
args: argparse.Namespace,
4646
request_body_read_callback: Callable[[bytes], None] = None,
47+
prepared_request_readiness: Callable[[niquests.PreparedRequest], None] = None,
4748
) -> Iterable[RequestsMessage]:
4849
httpie_session = None
4950
httpie_session_headers = None
@@ -88,7 +89,12 @@ def collect_messages(
8889
# TODO: reflect the split between request and send kwargs.
8990
dump_request(request_kwargs)
9091

91-
request = requests.Request(**request_kwargs)
92+
hooks = None
93+
94+
if prepared_request_readiness:
95+
hooks = {"pre_send": [prepared_request_readiness]}
96+
97+
request = niquests.Request(**request_kwargs, hooks=hooks)
9298
prepared_request = requests_session.prepare_request(request)
9399
transform_headers(request, prepared_request)
94100
if args.path_as_is:
@@ -124,7 +130,7 @@ def collect_messages(
124130
response_count += 1
125131
if response.next:
126132
if args.max_redirects and response_count == args.max_redirects:
127-
raise requests.TooManyRedirects
133+
raise niquests.TooManyRedirects
128134
if args.follow:
129135
prepared_request = response.next
130136
if args.all:
@@ -157,8 +163,8 @@ def build_requests_session(
157163
verify: bool,
158164
ssl_version: str = None,
159165
ciphers: str = None,
160-
) -> requests.Session:
161-
requests_session = requests.Session()
166+
) -> niquests.Session:
167+
requests_session = niquests.Session()
162168

163169
# Install our adapter.
164170
http_adapter = HTTPieHTTPAdapter()
@@ -186,7 +192,7 @@ def build_requests_session(
186192

187193
def dump_request(kwargs: dict):
188194
sys.stderr.write(
189-
f'\n>>> requests.request(**{repr_dict(kwargs)})\n\n')
195+
f'\n>>> niquests.request(**{repr_dict(kwargs)})\n\n')
190196

191197

192198
def finalize_headers(headers: HTTPHeadersDict) -> HTTPHeadersDict:
@@ -210,13 +216,13 @@ def finalize_headers(headers: HTTPHeadersDict) -> HTTPHeadersDict:
210216

211217

212218
def transform_headers(
213-
request: requests.Request,
214-
prepared_request: requests.PreparedRequest
219+
request: niquests.Request,
220+
prepared_request: niquests.PreparedRequest
215221
) -> None:
216222
"""Apply various transformations on top of the `prepared_requests`'s
217223
headers to change the request prepreation behavior."""
218224

219-
# Remove 'Content-Length' when it is misplaced by requests.
225+
# Remove 'Content-Length' when it is misplaced by niquests.
220226
if (
221227
prepared_request.method in IGNORE_CONTENT_LENGTH_METHODS
222228
and prepared_request.headers.get('Content-Length') == '0'
@@ -232,7 +238,7 @@ def transform_headers(
232238

233239
def apply_missing_repeated_headers(
234240
original_headers: HTTPHeadersDict,
235-
prepared_request: requests.PreparedRequest
241+
prepared_request: niquests.PreparedRequest
236242
) -> None:
237243
"""Update the given `prepared_request`'s headers with the original
238244
ones. This allows the requests to be prepared as usual, and then later
@@ -291,7 +297,7 @@ def make_send_kwargs_mergeable_from_env(args: argparse.Namespace) -> dict:
291297
cert = args.cert
292298
if args.cert_key:
293299
# Having a client certificate key passphrase is not supported
294-
# by requests. So we are using our own transportation structure
300+
# by niquests. So we are using our own transportation structure
295301
# which is compatible with their format (a tuple of minimum two
296302
# items).
297303
#
@@ -329,7 +335,7 @@ def make_request_kwargs(
329335
request_body_read_callback=lambda chunk: chunk
330336
) -> dict:
331337
"""
332-
Translate our `args` into `requests.Request` keyword arguments.
338+
Translate our `args` into `niquests.Request` keyword arguments.
333339
334340
"""
335341
files = args.files

httpie/core.py

+49-8
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
import socket
66
from typing import List, Optional, Union, Callable
77

8-
import requests
8+
import niquests
99
from pygments import __version__ as pygments_version
10-
from requests import __version__ as requests_version
10+
from niquests import __version__ as requests_version
1111

1212
from . import __version__ as httpie_version
1313
from .cli.constants import OUT_REQ_BODY
@@ -112,16 +112,16 @@ def handle_generic_error(e, annotation=None):
112112
if include_traceback:
113113
raise
114114
exit_status = ExitStatus.ERROR
115-
except requests.Timeout:
115+
except niquests.Timeout:
116116
exit_status = ExitStatus.ERROR_TIMEOUT
117117
env.log_error(f'Request timed out ({parsed_args.timeout}s).')
118-
except requests.TooManyRedirects:
118+
except niquests.TooManyRedirects:
119119
exit_status = ExitStatus.ERROR_TOO_MANY_REDIRECTS
120120
env.log_error(
121121
f'Too many redirects'
122122
f' (--max-redirects={parsed_args.max_redirects}).'
123123
)
124-
except requests.exceptions.ConnectionError as exc:
124+
except niquests.exceptions.ConnectionError as exc:
125125
annotation = None
126126
original_exc = unwrap_context(exc)
127127
if isinstance(original_exc, socket.gaierror):
@@ -175,8 +175,8 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
175175
# TODO: Refactor and drastically simplify, especially so that the separator logic is elsewhere.
176176
exit_status = ExitStatus.SUCCESS
177177
downloader = None
178-
initial_request: Optional[requests.PreparedRequest] = None
179-
final_response: Optional[requests.Response] = None
178+
initial_request: Optional[niquests.PreparedRequest] = None
179+
final_response: Optional[niquests.Response] = None
180180
processing_options = ProcessingOptions.from_raw_args(args)
181181

182182
def separate():
@@ -204,8 +204,46 @@ def request_body_read_callback(chunk: bytes):
204204
args.follow = True # --download implies --follow.
205205
downloader = Downloader(env, output_file=args.output_file, resume=args.download_resume)
206206
downloader.pre_request(args.headers)
207+
208+
def prepared_request_readiness(pr):
209+
nonlocal output_options, do_write_body, processing_options
210+
211+
if initial_request == pr:
212+
if args.debug and pr.conn_info and pr.conn_info.destination_address:
213+
print(f"* Connected to {pr.conn_info.destination_address[0]} port {pr.conn_info.destination_address[1]}")
214+
separate()
215+
216+
if args.debug and pr.conn_info:
217+
if pr.conn_info.cipher:
218+
print(f"* TLS connection using {pr.conn_info.tls_version.name.replace('_', '.')} / {pr.conn_info.cipher}")
219+
separate()
220+
221+
if pr.conn_info.certificate_dict:
222+
print("* Server certificate:")
223+
224+
if "subject" in pr.conn_info.certificate_dict:
225+
print(f"* subject: {pr.conn_info.certificate_dict['subject']}")
226+
227+
print(f"* start date: {pr.conn_info.certificate_dict['notBefore']}")
228+
print(f"* expire date: {pr.conn_info.certificate_dict['notAfter']}")
229+
230+
if "subjectAltName" in pr.conn_info.certificate_dict:
231+
print(f"* subjectAltName: {pr.conn_info.certificate_dict['subjectAltName']}")
232+
233+
print(f"* issuer: {pr.conn_info.certificate_dict['issuer']}")
234+
separate()
235+
236+
write_message(
237+
requests_message=pr,
238+
env=env,
239+
output_options=output_options._replace(
240+
body=do_write_body
241+
),
242+
processing_options=processing_options
243+
)
244+
207245
messages = collect_messages(env, args=args,
208-
request_body_read_callback=request_body_read_callback)
246+
request_body_read_callback=request_body_read_callback, prepared_request_readiness=prepared_request_readiness)
209247
force_separator = False
210248
prev_with_body = False
211249

@@ -225,6 +263,9 @@ def request_body_read_callback(chunk: bytes):
225263
is_streamed_upload = not isinstance(message.body, (str, bytes))
226264
do_write_body = not is_streamed_upload
227265
force_separator = is_streamed_upload and env.stdout_isatty
266+
if message.conn_info is None and not args.offline:
267+
prev_with_body = output_options.body
268+
continue
228269
else:
229270
final_response = message
230271
if args.check_status or downloader:

httpie/downloads.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from typing import IO, Optional, Tuple
1111
from urllib.parse import urlsplit
1212

13-
import requests
13+
import niquests
1414

1515
from .models import HTTPResponse, OutputOptions
1616
from .output.streams import RawStream
@@ -202,7 +202,7 @@ def pre_request(self, request_headers: dict):
202202
def start(
203203
self,
204204
initial_url: str,
205-
final_response: requests.Response
205+
final_response: niquests.Response
206206
) -> Tuple[RawStream, IO]:
207207
"""
208208
Initiate and return a stream for `response` body with progress
@@ -288,7 +288,7 @@ def chunk_downloaded(self, chunk: bytes):
288288
@staticmethod
289289
def _get_output_file_from_response(
290290
initial_url: str,
291-
final_response: requests.Response,
291+
final_response: niquests.Response,
292292
) -> IO:
293293
# Output file not specified. Pick a name that doesn't exist yet.
294294
filename = None

httpie/internal/update_warnings.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pathlib import Path
55
from typing import Any, Optional, Callable
66

7-
import requests
7+
import niquests
88

99
import httpie
1010
from httpie.context import Environment, LogLevel
@@ -41,7 +41,7 @@ def _fetch_updates(env: Environment) -> str:
4141
file = env.config.version_info_file
4242
data = _read_data_error_free(file)
4343

44-
response = requests.get(PACKAGE_INDEX_LINK, verify=False)
44+
response = niquests.get(PACKAGE_INDEX_LINK, verify=False)
4545
response.raise_for_status()
4646

4747
data.setdefault('last_warned_date', None)

httpie/manager/cli.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def generate_subparsers(root, parent_parser, definitions, spec):
133133
Managing interface for the HTTPie itself. <https://httpie.io/docs#manager>
134134
135135
Be aware that you might be looking for http/https commands for sending
136-
HTTP requests. This command is only available for managing the HTTTPie
136+
HTTP niquests. This command is only available for managing the HTTTPie
137137
plugins and the configuration around it.
138138
'''
139139
),

httpie/models.py

+12-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from time import monotonic
22

3-
import requests
3+
import niquests
44
from urllib3.util import SKIP_HEADER, SKIPPABLE_HEADERS
55

66
from enum import Enum, auto
@@ -59,7 +59,7 @@ def content_type(self) -> str:
5959

6060

6161
class HTTPResponse(HTTPMessage):
62-
"""A :class:`requests.models.Response` wrapper."""
62+
"""A :class:`niquests.models.Response` wrapper."""
6363

6464
def iter_body(self, chunk_size=1):
6565
return self._orig.iter_content(chunk_size=chunk_size)
@@ -112,7 +112,8 @@ def version(self) -> str:
112112
9: '0.9',
113113
10: '1.0',
114114
11: '1.1',
115-
20: '2.0',
115+
20: '2',
116+
30: '3',
116117
}
117118
fallback = 11
118119
version = None
@@ -128,7 +129,7 @@ def version(self) -> str:
128129

129130

130131
class HTTPRequest(HTTPMessage):
131-
"""A :class:`requests.models.Request` wrapper."""
132+
"""A :class:`niquests.models.Request` wrapper."""
132133

133134
def iter_body(self, chunk_size):
134135
yield self.body
@@ -140,10 +141,11 @@ def iter_lines(self, chunk_size):
140141
def headers(self):
141142
url = urlsplit(self._orig.url)
142143

143-
request_line = '{method} {path}{query} HTTP/1.1'.format(
144+
request_line = '{method} {path}{query} {http_version}'.format(
144145
method=self._orig.method,
145146
path=url.path or '/',
146-
query=f'?{url.query}' if url.query else ''
147+
query=f'?{url.query}' if url.query else '',
148+
http_version=self._orig.conn_info.http_version.value.replace(".0", "") if self._orig.conn_info and self._orig.conn_info.http_version else "HTTP/1.1"
147149
)
148150

149151
headers = self._orig.headers.copy()
@@ -158,6 +160,7 @@ def headers(self):
158160

159161
headers.insert(0, request_line)
160162
headers = '\r\n'.join(headers).strip()
163+
161164
return headers
162165

163166
@property
@@ -169,7 +172,7 @@ def body(self):
169172
return body or b''
170173

171174

172-
RequestsMessage = Union[requests.PreparedRequest, requests.Response]
175+
RequestsMessage = Union[niquests.PreparedRequest, niquests.Response]
173176

174177

175178
class RequestsMessageKind(Enum):
@@ -178,9 +181,9 @@ class RequestsMessageKind(Enum):
178181

179182

180183
def infer_requests_message_kind(message: RequestsMessage) -> RequestsMessageKind:
181-
if isinstance(message, requests.PreparedRequest):
184+
if isinstance(message, niquests.PreparedRequest):
182185
return RequestsMessageKind.REQUEST
183-
elif isinstance(message, requests.Response):
186+
elif isinstance(message, niquests.Response):
184187
return RequestsMessageKind.RESPONSE
185188
else:
186189
raise TypeError(f"Unexpected message type: {type(message).__name__}")

httpie/output/writer.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import errno
2-
import requests
2+
import niquests
33
from typing import Any, Dict, IO, Optional, TextIO, Tuple, Type, Union
44

55
from ..cli.dicts import HTTPHeadersDict
@@ -105,7 +105,7 @@ def write_raw_data(
105105
headers: Optional[HTTPHeadersDict] = None,
106106
stream_kwargs: Optional[Dict[str, Any]] = None
107107
):
108-
msg = requests.PreparedRequest()
108+
msg = niquests.PreparedRequest()
109109
msg.is_body_upload_chunk = True
110110
msg.body = data
111111
msg.headers = headers or HTTPHeadersDict()

0 commit comments

Comments
 (0)