From 0ffa1428f4b83074a10aacaa33bee90abf5775d9 Mon Sep 17 00:00:00 2001 From: Ahmed TAHRI Date: Wed, 3 Apr 2024 07:45:49 +0200 Subject: [PATCH] respect httpie config to generate path for "quic.json" cache layer --- httpie/client.py | 5 ++++- httpie/config.py | 6 +++++- httpie/ssl_.py | 16 ++++++---------- tests/test_h2n3.py | 44 +++++++++++++++++++++++++++++++++++++++++++- tests/test_meta.py | 6 +++++- 5 files changed, 63 insertions(+), 14 deletions(-) diff --git a/httpie/client.py b/httpie/client.py index d2149b23f3..397de49b5c 100644 --- a/httpie/client.py +++ b/httpie/client.py @@ -2,6 +2,7 @@ import json import sys import typing +from pathlib import Path from random import randint from time import monotonic from typing import Any, Dict, Callable, Iterable @@ -86,6 +87,7 @@ def collect_messages( disable_ipv6=args.ipv4, disable_ipv4=args.ipv6, source_address=source_address, + quic_cache=env.config.quic_file, ) parsed_url = parse_url(args.url) @@ -205,9 +207,10 @@ def build_requests_session( disable_ipv4: bool = False, disable_ipv6: bool = False, source_address: typing.Tuple[str, int] = None, + quic_cache: typing.Optional[Path] = None, ) -> niquests.Session: requests_session = niquests.Session() - requests_session.quic_cache_layer = QuicCapabilityCache() + requests_session.quic_cache_layer = QuicCapabilityCache(quic_cache) if resolver: resolver_rebuilt = [] diff --git a/httpie/config.py b/httpie/config.py index 27bc0a784d..5e6fc1245e 100644 --- a/httpie/config.py +++ b/httpie/config.py @@ -149,7 +149,7 @@ def __init__(self, directory: Union[str, Path] = DEFAULT_CONFIG_DIR): def default_options(self) -> list: return self['default_options'] - def _configured_path(self, config_option: str, default: str) -> None: + def _configured_path(self, config_option: str, default: str) -> Path: return Path( self.get(config_option, self.directory / default) ).expanduser().resolve() @@ -162,6 +162,10 @@ def plugins_dir(self) -> Path: def version_info_file(self) -> Path: return self._configured_path('version_info_file', 'version_info.json') + @property + def quic_file(self) -> Path: + return self._configured_path('quic_file', 'quic.json') + @property def developer_mode(self) -> bool: """This is a special setting for the development environment. It is diff --git a/httpie/ssl_.py b/httpie/ssl_.py index 292f03602c..715a203ff0 100644 --- a/httpie/ssl_.py +++ b/httpie/ssl_.py @@ -1,11 +1,10 @@ import ssl import typing +from pathlib import Path from typing import NamedTuple, Optional, Tuple, MutableMapping import json import os.path -from os import makedirs -from httpie.config import DEFAULT_CONFIG_DIR from httpie.adapters import HTTPAdapter # noinspection PyPackageRequirements from urllib3.util.ssl_ import ( @@ -38,21 +37,18 @@ class QuicCapabilityCache( See https://urllib3future.readthedocs.io/en/latest/advanced-usage.html#remembering-http-3-over-quic-support for the implementation guide.""" - __file__ = os.path.join(DEFAULT_CONFIG_DIR, "quic.json") - - def __init__(self): + def __init__(self, path: Path): + self._path = path self._cache = {} - if not os.path.exists(DEFAULT_CONFIG_DIR): - makedirs(DEFAULT_CONFIG_DIR, exist_ok=True) - if os.path.exists(QuicCapabilityCache.__file__): - with open(QuicCapabilityCache.__file__, "r") as fp: + if os.path.exists(path): + with open(path, "r") as fp: try: self._cache = json.load(fp) except json.JSONDecodeError: # if the file is corrupted (invalid json) then, ignore it. pass def save(self): - with open(QuicCapabilityCache.__file__, "w+") as fp: + with open(self._path, "w") as fp: json.dump(self._cache, fp) def __contains__(self, item: Tuple[str, int]): diff --git a/tests/test_h2n3.py b/tests/test_h2n3.py index 9219f3dfac..b7f24b36c0 100644 --- a/tests/test_h2n3.py +++ b/tests/test_h2n3.py @@ -1,4 +1,7 @@ -from .utils import HTTP_OK, http +import pytest +import json + +from .utils import HTTP_OK, http, PersistentMockEnvironment def test_should_not_do_http1_by_default(remote_httpbin_secure): @@ -33,3 +36,42 @@ def test_force_http3(remote_httpbin_secure): assert "HTTP/3" in r assert HTTP_OK in r + + +@pytest.fixture +def with_quic_cache_persistent(tmp_path): + env = PersistentMockEnvironment() + env.config['quic_file'] = tmp_path / 'quic.json' + yield env + env.cleanup(force=True) + + +def test_ensure_quic_cache(remote_httpbin_secure, with_quic_cache_persistent): + """ + This test aim to verify that the QuicCapabilityCache work as intended. + """ + r = http( + "--verify=no", + remote_httpbin_secure + '/get', + env=with_quic_cache_persistent + ) + + assert "HTTP/2" in r + assert HTTP_OK in r + + r = http( + "--verify=no", + remote_httpbin_secure + '/get', + env=with_quic_cache_persistent + ) + + assert "HTTP/3" in r + assert HTTP_OK in r + + tmp_path = with_quic_cache_persistent.config['quic_file'] + + with open(tmp_path, "r") as fp: + cache = json.load(fp) + + assert len(cache) == 1 + assert "pie.dev" in list(cache.keys())[0] diff --git a/tests/test_meta.py b/tests/test_meta.py index 7a5f57eb4d..fa8a7e4d20 100644 --- a/tests/test_meta.py +++ b/tests/test_meta.py @@ -11,7 +11,11 @@ def test_meta_elapsed_time(httpbin): def test_meta_extended_tls(remote_httpbin_secure): - r = http('--verify=no', '--meta', remote_httpbin_secure + '/get') + # using --verify=no may cause the certificate information not to display with Python < 3.10 + # it is guaranteed to be there when using HTTP/3 over QUIC. That's why we set the '--http3' flag. + # it's a known CPython limitation with getpeercert(binary_form=False). + r = http('--verify=no', '--http3', '--meta', remote_httpbin_secure + '/get') + assert 'Connected to' in r assert 'Connection secured using' in r assert 'Server certificate' in r