From dd85f1d1d786501fd67a8f5d23c7af251a3cee2f Mon Sep 17 00:00:00 2001 From: Ondřej Nosek Date: Apr 29 2026 03:13:39 +0000 Subject: Add configurable HTTP version for lookaside transfers Introduce lookaside_http_version config option to control HTTP protocol version used for source file downloads and uploads. Valid values: - 'auto' (default): Let curl negotiate (may use HTTP/2) - '1.1': Force HTTP/1.1 (better for high-latency connections) - '2': Force HTTP/2 (better for low-latency, multiplexing) Also include UPLOAD_BUFFERSIZE optimization (1MB buffer) with hasattr check for compatibility with older libcurl versions (RHEL 7). This allows users to optimize transfer performance based on their network characteristics while maintaining backward compatibility. Configuration example: [fedpkg] lookaside_http_version = 1.1 Inspired-by: https://pagure.io/rpkg/pull-request/772 Assisted-by: Claude Sonnet 4.5 Signed-off-by: Ondřej Nosek --- diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py index 2e49132..46ca599 100644 --- a/pyrpkg/__init__.py +++ b/pyrpkg/__init__.py @@ -122,7 +122,8 @@ class Commands(object): dist=None, target=None, quiet=False, distgit_namespaced=False, realms=None, lookaside_namespaced=False, git_excludes=None, results_dir='root', allow_pre_generated_srpm=False, - lookaside_attempts=None, lookaside_delay=None, koji_offline=False): + lookaside_attempts=None, lookaside_delay=None, lookaside_http_version='auto', + koji_offline=False): """Init the object and some configuration details.""" # Path to operate on, most often pwd @@ -258,6 +259,8 @@ class Commands(object): self.lookaside_attempts = lookaside_attempts # initial delay between network operation attempts. In seconds. self.lookaside_delay = lookaside_delay + # HTTP protocol version for lookaside transfers + self.lookaside_http_version = lookaside_http_version # prevent connecting to Koji self.koji_offline = koji_offline @@ -281,7 +284,8 @@ class Commands(object): return CGILookasideCache( self.lookasidehash, self.lookaside, self.lookaside_cgi, client_cert=self.cert_file, ca_cert=self.ca_cert, - attempts=self.lookaside_attempts, delay=self.lookaside_delay) + attempts=self.lookaside_attempts, delay=self.lookaside_delay, + http_version=self.lookaside_http_version) @property def path(self): diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py index 6e071d3..a6f7f0c 100644 --- a/pyrpkg/cli.py +++ b/pyrpkg/cli.py @@ -269,6 +269,7 @@ class cliClient(object): results_dir=results_dir, lookaside_attempts=self.lookaside_attempts, lookaside_delay=self.lookaside_delay, + lookaside_http_version=self.lookaside_http_version, koji_offline=koji_offline ) @@ -3210,6 +3211,21 @@ class cliClient(object): val = None return val + @property + def lookaside_http_version(self): + """loads parameter 'lookaside_http_version' from the config file + + Valid values: 'auto', '1.1', '2' + Default: 'auto' (let curl decide) + """ + if self.config.has_option(self.name, 'lookaside_http_version'): + val = self.config.get(self.name, 'lookaside_http_version') + if val in ('auto', '1.1', '2'): + return val + self.log.error("Error: The config value 'lookaside_http_version' " + "must be 'auto', '1.1', or '2'.") + return 'auto' + # this method has to be specified in a derived class def _check_token(self, token, token_type): raise rpkgError('Undefined method for checking the token.') diff --git a/pyrpkg/lookaside.py b/pyrpkg/lookaside.py index 689b8b6..1e88352 100644 --- a/pyrpkg/lookaside.py +++ b/pyrpkg/lookaside.py @@ -32,7 +32,8 @@ from .errors import AlreadyUploadedError, DownloadError, InvalidHashType, Upload class CGILookasideCache(object): """A class to interact with a CGI-based lookaside cache""" def __init__(self, hashtype, download_url, upload_url, - client_cert=None, ca_cert=None, attempts=None, delay=None): + client_cert=None, ca_cert=None, attempts=None, delay=None, + http_version='auto'): """Constructor :param str hashtype: The hash algorithm to use for uploads. (e.g 'md5') @@ -50,6 +51,9 @@ class CGILookasideCache(object): says how many tries to do. None = single attempt / no-retrying :param int delay: Initial delay between network operation attempts. Each attempt doubles the previous delay value. In seconds. + :param str http_version: HTTP protocol version to use. Valid values: + 'auto' (let curl decide), '1.1' (force HTTP/1.1), '2' (force HTTP/2). + Defaults to 'auto'. """ self.hashtype = hashtype self.download_url = download_url @@ -58,6 +62,7 @@ class CGILookasideCache(object): self.ca_cert = ca_cert self.attempts = attempts if attempts is not None and attempts > 1 else 1 self.delay_between_attempts = delay if delay is not None and delay >= 0 else 15 + self.http_version = http_version self.log = logging.getLogger(__name__) @@ -191,6 +196,10 @@ class CGILookasideCache(object): c.setopt(pycurl.LOW_SPEED_LIMIT, 1000) c.setopt(pycurl.LOW_SPEED_TIME, 60) c.setopt(pycurl.FOLLOWLOCATION, 1) + if self.http_version == '1.1': + c.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_1_1) + elif self.http_version == '2' and hasattr(pycurl, 'CURL_HTTP_VERSION_2_0'): + c.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_2_0) # call retry method directly instead of @retry decorator - this approach allows passing # object's internal variables into the retry method @@ -355,6 +364,12 @@ class CGILookasideCache(object): c.setopt(pycurl.NOPROGRESS, True) c.setopt(pycurl.HTTPPOST, post_data) c.setopt(pycurl.FOLLOWLOCATION, 1) + if hasattr(pycurl, 'UPLOAD_BUFFERSIZE'): + c.setopt(pycurl.UPLOAD_BUFFERSIZE, 1024 * 1024) + if self.http_version == '1.1': + c.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_1_1) + elif self.http_version == '2' and hasattr(pycurl, 'CURL_HTTP_VERSION_2_0'): + c.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_2_0) if self.client_cert is not None: if os.path.exists(self.client_cert):