Path: blob/main/external/curl/tests/http/testenv/nghttpx.py
2659 views
#!/usr/bin/env python31# -*- coding: utf-8 -*-2#***************************************************************************3# _ _ ____ _4# Project ___| | | | _ \| |5# / __| | | | |_) | |6# | (__| |_| | _ <| |___7# \___|\___/|_| \_\_____|8#9# Copyright (C) Daniel Stenberg, <[email protected]>, et al.10#11# This software is licensed as described in the file COPYING, which12# you should have received as part of this distribution. The terms13# are also available at https://curl.se/docs/copyright.html.14#15# You may opt to use, copy, modify, merge, publish, distribute and/or sell16# copies of the Software, and permit persons to whom the Software is17# furnished to do so, under the terms of the COPYING file.18#19# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY20# KIND, either express or implied.21#22# SPDX-License-Identifier: curl23#24###########################################################################25#26import logging27import os28import signal29import socket30import subprocess31import time32from typing import Optional, Dict33from datetime import datetime, timedelta3435from .env import Env, NghttpxUtil36from .curl import CurlClient37from .ports import alloc_ports_and_do3839log = logging.getLogger(__name__)404142class Nghttpx:4344def __init__(self, env: Env, name: str, domain: str, cred_name: str):45self.env = env46self._name = name47self._domain = domain48self._port = 049self._https_port = 050self._cmd = env.nghttpx51self._run_dir = os.path.join(env.gen_dir, name)52self._pid_file = os.path.join(self._run_dir, 'nghttpx.pid')53self._conf_file = os.path.join(self._run_dir, 'nghttpx.conf')54self._error_log = os.path.join(self._run_dir, 'nghttpx.log')55self._stderr = os.path.join(self._run_dir, 'nghttpx.stderr')56self._tmp_dir = os.path.join(self._run_dir, 'tmp')57self._process: Optional[subprocess.Popen] = None58self._cred_name = self._def_cred_name = cred_name59self._loaded_cred_name = ''60self._version = NghttpxUtil.version(self._cmd)6162def supports_h3(self):63return NghttpxUtil.version_with_h3(self._version)6465def set_cred_name(self, name: str):66self._cred_name = name6768def reset_config(self):69self._cred_name = self._def_cred_name7071def reload_if_config_changed(self):72if self._process and self._port > 0 and \73self._loaded_cred_name == self._cred_name:74return True75return self.reload()7677@property78def https_port(self):79return self._https_port8081def exists(self):82return self._cmd and os.path.exists(self._cmd)8384def clear_logs(self):85self._rmf(self._error_log)86self._rmf(self._stderr)8788def is_running(self):89if self._process:90self._process.poll()91return self._process.returncode is None92return False9394def start_if_needed(self):95if not self.is_running():96return self.start()97return True9899def initial_start(self):100self._rmf(self._pid_file)101self._rmf(self._error_log)102self._mkpath(self._run_dir)103self._write_config()104105def start(self, wait_live=True):106pass107108def stop(self, wait_dead=True):109self._mkpath(self._tmp_dir)110if self._process:111self._process.terminate()112self._process.wait(timeout=2)113self._process = None114return not wait_dead or self.wait_dead(timeout=timedelta(seconds=5))115return True116117def restart(self):118self.stop()119return self.start()120121def reload(self, timeout: timedelta = timedelta(seconds=Env.SERVER_TIMEOUT)):122if self._process:123running = self._process124self._process = None125os.kill(running.pid, signal.SIGQUIT)126end_wait = datetime.now() + timedelta(seconds=5)127if not self.start(wait_live=False):128self._process = running129return False130while datetime.now() < end_wait:131try:132log.debug(f'waiting for nghttpx({running.pid}) to exit.')133running.wait(1)134log.debug(f'nghttpx({running.pid}) terminated -> {running.returncode}')135running = None136break137except subprocess.TimeoutExpired:138log.warning(f'nghttpx({running.pid}), not shut down yet.')139os.kill(running.pid, signal.SIGQUIT)140if datetime.now() >= end_wait:141log.error(f'nghttpx({running.pid}), terminate forcefully.')142os.kill(running.pid, signal.SIGKILL)143running.terminate()144running.wait(1)145return self.wait_live(timeout=timeout)146return False147148def wait_dead(self, timeout: timedelta):149curl = CurlClient(env=self.env, run_dir=self._tmp_dir)150try_until = datetime.now() + timeout151while datetime.now() < try_until:152if self._https_port > 0:153check_url = f'https://{self._domain}:{self._port}/'154r = curl.http_get(url=check_url, extra_args=[155'--trace', 'curl.trace', '--trace-time',156'--connect-timeout', '1'157])158else:159check_url = f'https://{self._domain}:{self._port}/'160r = curl.http_get(url=check_url, extra_args=[161'--trace', 'curl.trace', '--trace-time',162'--http3-only', '--connect-timeout', '1'163])164if r.exit_code != 0:165return True166log.debug(f'waiting for nghttpx to stop responding: {r}')167time.sleep(.1)168log.debug(f"Server still responding after {timeout}")169return False170171def wait_live(self, timeout: timedelta):172curl = CurlClient(env=self.env, run_dir=self._tmp_dir)173try_until = datetime.now() + timeout174while datetime.now() < try_until:175if self._https_port > 0:176check_url = f'https://{self._domain}:{self._port}/'177r = curl.http_get(url=check_url, extra_args=[178'--trace', 'curl.trace', '--trace-time',179'--connect-timeout', '1'180])181else:182check_url = f'https://{self._domain}:{self._port}/'183r = curl.http_get(url=check_url, extra_args=[184'--http3-only', '--trace', 'curl.trace', '--trace-time',185'--connect-timeout', '1'186])187if r.exit_code == 0:188return True189time.sleep(.1)190log.error(f"Server still not responding after {timeout}")191return False192193def _rmf(self, path):194if os.path.exists(path):195return os.remove(path)196197def _mkpath(self, path):198if not os.path.exists(path):199return os.makedirs(path)200201def _write_config(self):202with open(self._conf_file, 'w') as fd:203fd.write('# nghttpx test config')204fd.write("\n".join([205'# do we need something here?'206]))207208209class NghttpxQuic(Nghttpx):210211PORT_SPECS = {212'nghttpx_https': socket.SOCK_STREAM,213}214215def __init__(self, env: Env):216super().__init__(env=env, name='nghttpx-quic',217domain=env.domain1, cred_name=env.domain1)218self._https_port = env.https_port219220def initial_start(self):221super().initial_start()222223def startup(ports: Dict[str, int]) -> bool:224self._port = ports['nghttpx_https']225if self.start():226self.env.update_ports(ports)227return True228self.stop()229self._port = 0230return False231232return alloc_ports_and_do(NghttpxQuic.PORT_SPECS, startup,233self.env.gen_root, max_tries=3)234235def start(self, wait_live=True):236self._mkpath(self._tmp_dir)237if self._process:238self.stop()239creds = self.env.get_credentials(self._cred_name)240assert creds # convince pytype this isn't None241self._loaded_cred_name = self._cred_name242args = [self._cmd, f'--frontend=*,{self._port};tls']243if self.supports_h3():244args.extend([245f'--frontend=*,{self.env.h3_port};quic',246'--frontend-quic-early-data',247])248args.extend([249f'--backend=127.0.0.1,{self.env.https_port};{self._domain};sni={self._domain};proto=h2;tls',250f'--backend=127.0.0.1,{self.env.http_port}',251'--log-level=ERROR',252f'--pid-file={self._pid_file}',253f'--errorlog-file={self._error_log}',254f'--conf={self._conf_file}',255f'--cacert={self.env.ca.cert_file}',256creds.pkey_file,257creds.cert_file,258'--frontend-http3-window-size=1M',259'--frontend-http3-max-window-size=10M',260'--frontend-http3-connection-window-size=10M',261'--frontend-http3-max-connection-window-size=100M',262# f'--frontend-quic-debug-log',263])264ngerr = open(self._stderr, 'a')265self._process = subprocess.Popen(args=args, stderr=ngerr)266if self._process.returncode is not None:267return False268return not wait_live or self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT))269270271class NghttpxFwd(Nghttpx):272273def __init__(self, env: Env):274super().__init__(env=env, name='nghttpx-fwd',275domain=env.proxy_domain,276cred_name=env.proxy_domain)277278def initial_start(self):279super().initial_start()280281def startup(ports: Dict[str, int]) -> bool:282self._port = ports['h2proxys']283if self.start():284self.env.update_ports(ports)285return True286self.stop()287self._port = 0288return False289290return alloc_ports_and_do({'h2proxys': socket.SOCK_STREAM},291startup, self.env.gen_root, max_tries=3)292293def start(self, wait_live=True):294assert self._port > 0295self._mkpath(self._tmp_dir)296if self._process:297self.stop()298creds = self.env.get_credentials(self._cred_name)299assert creds # convince pytype this isn't None300self._loaded_cred_name = self._cred_name301args = [302self._cmd,303'--http2-proxy',304f'--frontend=*,{self._port}',305f'--backend=127.0.0.1,{self.env.proxy_port}',306'--log-level=ERROR',307f'--pid-file={self._pid_file}',308f'--errorlog-file={self._error_log}',309f'--conf={self._conf_file}',310f'--cacert={self.env.ca.cert_file}',311creds.pkey_file,312creds.cert_file,313]314ngerr = open(self._stderr, 'a')315self._process = subprocess.Popen(args=args, stderr=ngerr)316if self._process.returncode is not None:317return False318return not wait_live or self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT))319320def wait_dead(self, timeout: timedelta):321curl = CurlClient(env=self.env, run_dir=self._tmp_dir)322try_until = datetime.now() + timeout323while datetime.now() < try_until:324check_url = f'https://{self.env.proxy_domain}:{self._port}/'325r = curl.http_get(url=check_url)326if r.exit_code != 0:327return True328log.debug(f'waiting for nghttpx-fwd to stop responding: {r}')329time.sleep(.1)330log.debug(f"Server still responding after {timeout}")331return False332333def wait_live(self, timeout: timedelta):334curl = CurlClient(env=self.env, run_dir=self._tmp_dir)335try_until = datetime.now() + timeout336while datetime.now() < try_until:337check_url = f'https://{self.env.proxy_domain}:{self._port}/'338r = curl.http_get(url=check_url, extra_args=[339'--trace', 'curl.trace', '--trace-time'340])341if r.exit_code == 0:342return True343time.sleep(.1)344log.error(f"Server still not responding after {timeout}")345return False346347348