Path: blob/main/external/curl/tests/http/testenv/nghttpx.py
2066 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 Env36from .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._rmf(self._pid_file)61self._rmf(self._error_log)62self._mkpath(self._run_dir)63self._write_config()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):100pass101102def start(self, wait_live=True):103pass104105def stop(self, wait_dead=True):106self._mkpath(self._tmp_dir)107if self._process:108self._process.terminate()109self._process.wait(timeout=2)110self._process = None111return not wait_dead or self.wait_dead(timeout=timedelta(seconds=5))112return True113114def restart(self):115self.stop()116return self.start()117118def reload(self, timeout: timedelta = timedelta(seconds=Env.SERVER_TIMEOUT)):119if self._process:120running = self._process121self._process = None122os.kill(running.pid, signal.SIGQUIT)123end_wait = datetime.now() + timeout124if not self.start(wait_live=False):125self._process = running126return False127while datetime.now() < end_wait:128try:129log.debug(f'waiting for nghttpx({running.pid}) to exit.')130running.wait(2)131log.debug(f'nghttpx({running.pid}) terminated -> {running.returncode}')132break133except subprocess.TimeoutExpired:134log.warning(f'nghttpx({running.pid}), not shut down yet.')135os.kill(running.pid, signal.SIGQUIT)136if datetime.now() >= end_wait:137log.error(f'nghttpx({running.pid}), terminate forcefully.')138os.kill(running.pid, signal.SIGKILL)139running.terminate()140running.wait(1)141return self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT))142return False143144def wait_dead(self, timeout: timedelta):145curl = CurlClient(env=self.env, run_dir=self._tmp_dir)146try_until = datetime.now() + timeout147while datetime.now() < try_until:148if self._https_port > 0:149check_url = f'https://{self._domain}:{self._port}/'150r = curl.http_get(url=check_url, extra_args=[151'--trace', 'curl.trace', '--trace-time',152'--connect-timeout', '1'153])154else:155check_url = f'https://{self._domain}:{self._port}/'156r = curl.http_get(url=check_url, extra_args=[157'--trace', 'curl.trace', '--trace-time',158'--http3-only', '--connect-timeout', '1'159])160if r.exit_code != 0:161return True162log.debug(f'waiting for nghttpx to stop responding: {r}')163time.sleep(.1)164log.debug(f"Server still responding after {timeout}")165return False166167def wait_live(self, timeout: timedelta):168curl = CurlClient(env=self.env, run_dir=self._tmp_dir)169try_until = datetime.now() + timeout170while datetime.now() < try_until:171if self._https_port > 0:172check_url = f'https://{self._domain}:{self._port}/'173r = curl.http_get(url=check_url, extra_args=[174'--trace', 'curl.trace', '--trace-time',175'--connect-timeout', '1'176])177else:178check_url = f'https://{self._domain}:{self._port}/'179r = curl.http_get(url=check_url, extra_args=[180'--http3-only', '--trace', 'curl.trace', '--trace-time',181'--connect-timeout', '1'182])183if r.exit_code == 0:184return True185time.sleep(.1)186log.error(f"Server still not responding after {timeout}")187return False188189def _rmf(self, path):190if os.path.exists(path):191return os.remove(path)192193def _mkpath(self, path):194if not os.path.exists(path):195return os.makedirs(path)196197def _write_config(self):198with open(self._conf_file, 'w') as fd:199fd.write('# nghttpx test config')200fd.write("\n".join([201'# do we need something here?'202]))203204205class NghttpxQuic(Nghttpx):206207PORT_SPECS = {208'nghttpx_https': socket.SOCK_STREAM,209}210211def __init__(self, env: Env):212super().__init__(env=env, name='nghttpx-quic',213domain=env.domain1, cred_name=env.domain1)214self._https_port = env.https_port215216def initial_start(self):217218def startup(ports: Dict[str, int]) -> bool:219self._port = ports['nghttpx_https']220if self.start():221self.env.update_ports(ports)222return True223self.stop()224self._port = 0225return False226227return alloc_ports_and_do(NghttpxQuic.PORT_SPECS, startup,228self.env.gen_root, max_tries=3)229230def start(self, wait_live=True):231self._mkpath(self._tmp_dir)232if self._process:233self.stop()234creds = self.env.get_credentials(self._cred_name)235assert creds # convince pytype this isn't None236self._loaded_cred_name = self._cred_name237args = [238self._cmd,239f'--frontend=*,{self._port};tls',240f'--frontend=*,{self.env.h3_port};quic',241'--frontend-quic-early-data',242f'--backend=127.0.0.1,{self.env.https_port};{self._domain};sni={self._domain};proto=h2;tls',243f'--backend=127.0.0.1,{self.env.http_port}',244'--log-level=ERROR',245f'--pid-file={self._pid_file}',246f'--errorlog-file={self._error_log}',247f'--conf={self._conf_file}',248f'--cacert={self.env.ca.cert_file}',249creds.pkey_file,250creds.cert_file,251'--frontend-http3-window-size=1M',252'--frontend-http3-max-window-size=10M',253'--frontend-http3-connection-window-size=10M',254'--frontend-http3-max-connection-window-size=100M',255# f'--frontend-quic-debug-log',256]257ngerr = open(self._stderr, 'a')258self._process = subprocess.Popen(args=args, stderr=ngerr)259if self._process.returncode is not None:260return False261return not wait_live or self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT))262263264class NghttpxFwd(Nghttpx):265266def __init__(self, env: Env):267super().__init__(env=env, name='nghttpx-fwd',268domain=env.proxy_domain,269cred_name=env.proxy_domain)270271def initial_start(self):272273def startup(ports: Dict[str, int]) -> bool:274self._port = ports['h2proxys']275if self.start():276self.env.update_ports(ports)277return True278self.stop()279self._port = 0280return False281282return alloc_ports_and_do({'h2proxys': socket.SOCK_STREAM},283startup, self.env.gen_root, max_tries=3)284285def start(self, wait_live=True):286assert self._port > 0287self._mkpath(self._tmp_dir)288if self._process:289self.stop()290creds = self.env.get_credentials(self._cred_name)291assert creds # convince pytype this isn't None292self._loaded_cred_name = self._cred_name293args = [294self._cmd,295'--http2-proxy',296f'--frontend=*,{self._port}',297f'--backend=127.0.0.1,{self.env.proxy_port}',298'--log-level=ERROR',299f'--pid-file={self._pid_file}',300f'--errorlog-file={self._error_log}',301f'--conf={self._conf_file}',302f'--cacert={self.env.ca.cert_file}',303creds.pkey_file,304creds.cert_file,305]306ngerr = open(self._stderr, 'a')307self._process = subprocess.Popen(args=args, stderr=ngerr)308if self._process.returncode is not None:309return False310return not wait_live or self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT))311312def wait_dead(self, timeout: timedelta):313curl = CurlClient(env=self.env, run_dir=self._tmp_dir)314try_until = datetime.now() + timeout315while datetime.now() < try_until:316check_url = f'https://{self.env.proxy_domain}:{self._port}/'317r = curl.http_get(url=check_url)318if r.exit_code != 0:319return True320log.debug(f'waiting for nghttpx-fwd to stop responding: {r}')321time.sleep(.1)322log.debug(f"Server still responding after {timeout}")323return False324325def wait_live(self, timeout: timedelta):326curl = CurlClient(env=self.env, run_dir=self._tmp_dir)327try_until = datetime.now() + timeout328while datetime.now() < try_until:329check_url = f'https://{self.env.proxy_domain}:{self._port}/'330r = curl.http_get(url=check_url, extra_args=[331'--trace', 'curl.trace', '--trace-time'332])333if r.exit_code == 0:334return True335time.sleep(.1)336log.error(f"Server still not responding after {timeout}")337return False338339340