From 1379a2e2819ec89b7df5192c1d772648c82fc3f9 Mon Sep 17 00:00:00 2001 From: Ahmed TAHRI Date: Sun, 13 Oct 2024 20:09:35 +0200 Subject: [PATCH] add support for early responses 1xx (informational) --- CHANGELOG.md | 3 ++- httpie/client.py | 8 +++++--- httpie/core.py | 24 +++++++++++++++--------- setup.cfg | 2 +- tests/test_early_response.py | 11 +++++++++++ 5 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 tests/test_early_response.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ffdd691..dd200332 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,11 @@ This document records all notable changes to [HTTPie](https://httpie.io). This project adheres to [Semantic Versioning](https://semver.org/). -## [4.0.0.b1](https://github.com/httpie/cli/compare/3.2.2...master) (unreleased) +## [4.0.0](https://github.com/httpie/cli/compare/3.2.3...master) (unreleased) - Switched from the [`requests`](https://github.com/psf/requests) library to the compatible [`niquests`](https://github.com/jawah/niquests). ([#1531](https://github.com/httpie/cli/pull/1531)) - Added support for HTTP/2, and HTTP/3 protocols. ([#523](https://github.com/httpie/cli/issues/523), [#692](https://github.com/httpie/cli/issues/692), [#1531](https://github.com/httpie/cli/pull/1531)) +- Added support for early (informational) responses. ([#752](https://github.com/httpie/cli/issues/752)) ([#1531](https://github.com/httpie/cli/pull/1531)) - Added support for IPv4/IPv6 enforcement with `-6` and `-4`. ([#94](https://github.com/httpie/cli/issues/94), [#1531](https://github.com/httpie/cli/pull/1531)) - Added support for alternative DNS resolvers via `--resolver`. DNS over HTTPS, DNS over TLS, DNS over QUIC, and DNS over UDP are accepted. ([#99](https://github.com/httpie/cli/issues/99), [#1531](https://github.com/httpie/cli/pull/1531)) - Added support for binding to a specific network adapter with `--interface`. ([#1422](https://github.com/httpie/cli/issues/1422), [#1531](https://github.com/httpie/cli/pull/1531)) diff --git a/httpie/client.py b/httpie/client.py index 27811b4e..a771ac54 100644 --- a/httpie/client.py +++ b/httpie/client.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import argparse import json import sys @@ -43,7 +45,7 @@ def collect_messages( env: Environment, args: argparse.Namespace, request_body_read_callback: Callable[[bytes], None] = None, - prepared_request_readiness: Callable[[niquests.PreparedRequest], None] = None, + request_or_response_callback: Callable[[niquests.PreparedRequest | niquests.Response], None] = None, ) -> Iterable[RequestsMessage]: httpie_session = None httpie_session_headers = None @@ -155,8 +157,8 @@ def collect_messages( # It will help us yield the request before it is # actually sent. This will permit us to know about # the connection information for example. - if prepared_request_readiness: - hooks = {"pre_send": [prepared_request_readiness]} + if request_or_response_callback: + hooks = {"pre_send": [request_or_response_callback], "early_response": [request_or_response_callback]} request = niquests.Request(**request_kwargs, hooks=hooks) prepared_request = requests_session.prepare_request(request) diff --git a/httpie/core.py b/httpie/core.py index 28b0af28..ccd44779 100644 --- a/httpie/core.py +++ b/httpie/core.py @@ -3,6 +3,7 @@ import os import platform import sys import socket +from time import monotonic from typing import List, Optional, Union, Callable import niquests @@ -211,21 +212,26 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus: downloader = Downloader(env, output_file=args.output_file, resume=args.download_resume) downloader.pre_request(args.headers) - def prepared_request_readiness(pr): - """This callback is meant to output the request part. It is triggered by - the underlying Niquests library just after establishing the connection.""" + def request_or_response_callback(delayed_message): + """This callback is called in two scenario: + + (i) just after initializing a connection to remote host + (ii) an early response has been received (1xx responses)""" oo = OutputOptions.from_message( - pr, + delayed_message, args.output_options ) - oo = oo._replace( - body=isinstance(pr.body, (str, bytes)) and (args.verbose or oo.body) - ) + if hasattr(delayed_message, "body"): + oo = oo._replace( + body=isinstance(delayed_message.body, (str, bytes)) and (args.verbose or oo.body) + ) + else: + delayed_message._httpie_headers_parsed_at = monotonic() write_message( - requests_message=pr, + requests_message=delayed_message, env=env, output_options=oo, processing_options=processing_options @@ -238,7 +244,7 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus: env, args=args, request_body_read_callback=request_body_read_callback, - prepared_request_readiness=prepared_request_readiness + request_or_response_callback=request_or_response_callback ) force_separator = False diff --git a/setup.cfg b/setup.cfg index cc71785d..9d7c0e9c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -71,7 +71,7 @@ install_requires = pip charset_normalizer>=2.0.0 defusedxml>=0.6.0 - niquests[socks]>=3.7 + niquests[socks]>=3.9 Pygments>=2.5.2 setuptools importlib-metadata>=1.4.0; python_version<"3.8" diff --git a/tests/test_early_response.py b/tests/test_early_response.py new file mode 100644 index 00000000..809c098b --- /dev/null +++ b/tests/test_early_response.py @@ -0,0 +1,11 @@ +from .utils import http + + +def test_early_response_show(remote_httpbin_secure): + r = http( + "--verify=no", + 'https://early-hints.fastlylabs.com/' + ) + + assert "103 Early Hints" in r + assert "200 OK" in r