Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/external/curl/tests/http/testenv/nghttpx.py
2066 views
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
#***************************************************************************
4
# _ _ ____ _
5
# Project ___| | | | _ \| |
6
# / __| | | | |_) | |
7
# | (__| |_| | _ <| |___
8
# \___|\___/|_| \_\_____|
9
#
10
# Copyright (C) Daniel Stenberg, <[email protected]>, et al.
11
#
12
# This software is licensed as described in the file COPYING, which
13
# you should have received as part of this distribution. The terms
14
# are also available at https://curl.se/docs/copyright.html.
15
#
16
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
17
# copies of the Software, and permit persons to whom the Software is
18
# furnished to do so, under the terms of the COPYING file.
19
#
20
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21
# KIND, either express or implied.
22
#
23
# SPDX-License-Identifier: curl
24
#
25
###########################################################################
26
#
27
import logging
28
import os
29
import signal
30
import socket
31
import subprocess
32
import time
33
from typing import Optional, Dict
34
from datetime import datetime, timedelta
35
36
from .env import Env
37
from .curl import CurlClient
38
from .ports import alloc_ports_and_do
39
40
log = logging.getLogger(__name__)
41
42
43
class Nghttpx:
44
45
def __init__(self, env: Env, name: str, domain: str, cred_name: str):
46
self.env = env
47
self._name = name
48
self._domain = domain
49
self._port = 0
50
self._https_port = 0
51
self._cmd = env.nghttpx
52
self._run_dir = os.path.join(env.gen_dir, name)
53
self._pid_file = os.path.join(self._run_dir, 'nghttpx.pid')
54
self._conf_file = os.path.join(self._run_dir, 'nghttpx.conf')
55
self._error_log = os.path.join(self._run_dir, 'nghttpx.log')
56
self._stderr = os.path.join(self._run_dir, 'nghttpx.stderr')
57
self._tmp_dir = os.path.join(self._run_dir, 'tmp')
58
self._process: Optional[subprocess.Popen] = None
59
self._cred_name = self._def_cred_name = cred_name
60
self._loaded_cred_name = ''
61
self._rmf(self._pid_file)
62
self._rmf(self._error_log)
63
self._mkpath(self._run_dir)
64
self._write_config()
65
66
def set_cred_name(self, name: str):
67
self._cred_name = name
68
69
def reset_config(self):
70
self._cred_name = self._def_cred_name
71
72
def reload_if_config_changed(self):
73
if self._process and self._port > 0 and \
74
self._loaded_cred_name == self._cred_name:
75
return True
76
return self.reload()
77
78
@property
79
def https_port(self):
80
return self._https_port
81
82
def exists(self):
83
return self._cmd and os.path.exists(self._cmd)
84
85
def clear_logs(self):
86
self._rmf(self._error_log)
87
self._rmf(self._stderr)
88
89
def is_running(self):
90
if self._process:
91
self._process.poll()
92
return self._process.returncode is None
93
return False
94
95
def start_if_needed(self):
96
if not self.is_running():
97
return self.start()
98
return True
99
100
def initial_start(self):
101
pass
102
103
def start(self, wait_live=True):
104
pass
105
106
def stop(self, wait_dead=True):
107
self._mkpath(self._tmp_dir)
108
if self._process:
109
self._process.terminate()
110
self._process.wait(timeout=2)
111
self._process = None
112
return not wait_dead or self.wait_dead(timeout=timedelta(seconds=5))
113
return True
114
115
def restart(self):
116
self.stop()
117
return self.start()
118
119
def reload(self, timeout: timedelta = timedelta(seconds=Env.SERVER_TIMEOUT)):
120
if self._process:
121
running = self._process
122
self._process = None
123
os.kill(running.pid, signal.SIGQUIT)
124
end_wait = datetime.now() + timeout
125
if not self.start(wait_live=False):
126
self._process = running
127
return False
128
while datetime.now() < end_wait:
129
try:
130
log.debug(f'waiting for nghttpx({running.pid}) to exit.')
131
running.wait(2)
132
log.debug(f'nghttpx({running.pid}) terminated -> {running.returncode}')
133
break
134
except subprocess.TimeoutExpired:
135
log.warning(f'nghttpx({running.pid}), not shut down yet.')
136
os.kill(running.pid, signal.SIGQUIT)
137
if datetime.now() >= end_wait:
138
log.error(f'nghttpx({running.pid}), terminate forcefully.')
139
os.kill(running.pid, signal.SIGKILL)
140
running.terminate()
141
running.wait(1)
142
return self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT))
143
return False
144
145
def wait_dead(self, timeout: timedelta):
146
curl = CurlClient(env=self.env, run_dir=self._tmp_dir)
147
try_until = datetime.now() + timeout
148
while datetime.now() < try_until:
149
if self._https_port > 0:
150
check_url = f'https://{self._domain}:{self._port}/'
151
r = curl.http_get(url=check_url, extra_args=[
152
'--trace', 'curl.trace', '--trace-time',
153
'--connect-timeout', '1'
154
])
155
else:
156
check_url = f'https://{self._domain}:{self._port}/'
157
r = curl.http_get(url=check_url, extra_args=[
158
'--trace', 'curl.trace', '--trace-time',
159
'--http3-only', '--connect-timeout', '1'
160
])
161
if r.exit_code != 0:
162
return True
163
log.debug(f'waiting for nghttpx to stop responding: {r}')
164
time.sleep(.1)
165
log.debug(f"Server still responding after {timeout}")
166
return False
167
168
def wait_live(self, timeout: timedelta):
169
curl = CurlClient(env=self.env, run_dir=self._tmp_dir)
170
try_until = datetime.now() + timeout
171
while datetime.now() < try_until:
172
if self._https_port > 0:
173
check_url = f'https://{self._domain}:{self._port}/'
174
r = curl.http_get(url=check_url, extra_args=[
175
'--trace', 'curl.trace', '--trace-time',
176
'--connect-timeout', '1'
177
])
178
else:
179
check_url = f'https://{self._domain}:{self._port}/'
180
r = curl.http_get(url=check_url, extra_args=[
181
'--http3-only', '--trace', 'curl.trace', '--trace-time',
182
'--connect-timeout', '1'
183
])
184
if r.exit_code == 0:
185
return True
186
time.sleep(.1)
187
log.error(f"Server still not responding after {timeout}")
188
return False
189
190
def _rmf(self, path):
191
if os.path.exists(path):
192
return os.remove(path)
193
194
def _mkpath(self, path):
195
if not os.path.exists(path):
196
return os.makedirs(path)
197
198
def _write_config(self):
199
with open(self._conf_file, 'w') as fd:
200
fd.write('# nghttpx test config')
201
fd.write("\n".join([
202
'# do we need something here?'
203
]))
204
205
206
class NghttpxQuic(Nghttpx):
207
208
PORT_SPECS = {
209
'nghttpx_https': socket.SOCK_STREAM,
210
}
211
212
def __init__(self, env: Env):
213
super().__init__(env=env, name='nghttpx-quic',
214
domain=env.domain1, cred_name=env.domain1)
215
self._https_port = env.https_port
216
217
def initial_start(self):
218
219
def startup(ports: Dict[str, int]) -> bool:
220
self._port = ports['nghttpx_https']
221
if self.start():
222
self.env.update_ports(ports)
223
return True
224
self.stop()
225
self._port = 0
226
return False
227
228
return alloc_ports_and_do(NghttpxQuic.PORT_SPECS, startup,
229
self.env.gen_root, max_tries=3)
230
231
def start(self, wait_live=True):
232
self._mkpath(self._tmp_dir)
233
if self._process:
234
self.stop()
235
creds = self.env.get_credentials(self._cred_name)
236
assert creds # convince pytype this isn't None
237
self._loaded_cred_name = self._cred_name
238
args = [
239
self._cmd,
240
f'--frontend=*,{self._port};tls',
241
f'--frontend=*,{self.env.h3_port};quic',
242
'--frontend-quic-early-data',
243
f'--backend=127.0.0.1,{self.env.https_port};{self._domain};sni={self._domain};proto=h2;tls',
244
f'--backend=127.0.0.1,{self.env.http_port}',
245
'--log-level=ERROR',
246
f'--pid-file={self._pid_file}',
247
f'--errorlog-file={self._error_log}',
248
f'--conf={self._conf_file}',
249
f'--cacert={self.env.ca.cert_file}',
250
creds.pkey_file,
251
creds.cert_file,
252
'--frontend-http3-window-size=1M',
253
'--frontend-http3-max-window-size=10M',
254
'--frontend-http3-connection-window-size=10M',
255
'--frontend-http3-max-connection-window-size=100M',
256
# f'--frontend-quic-debug-log',
257
]
258
ngerr = open(self._stderr, 'a')
259
self._process = subprocess.Popen(args=args, stderr=ngerr)
260
if self._process.returncode is not None:
261
return False
262
return not wait_live or self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT))
263
264
265
class NghttpxFwd(Nghttpx):
266
267
def __init__(self, env: Env):
268
super().__init__(env=env, name='nghttpx-fwd',
269
domain=env.proxy_domain,
270
cred_name=env.proxy_domain)
271
272
def initial_start(self):
273
274
def startup(ports: Dict[str, int]) -> bool:
275
self._port = ports['h2proxys']
276
if self.start():
277
self.env.update_ports(ports)
278
return True
279
self.stop()
280
self._port = 0
281
return False
282
283
return alloc_ports_and_do({'h2proxys': socket.SOCK_STREAM},
284
startup, self.env.gen_root, max_tries=3)
285
286
def start(self, wait_live=True):
287
assert self._port > 0
288
self._mkpath(self._tmp_dir)
289
if self._process:
290
self.stop()
291
creds = self.env.get_credentials(self._cred_name)
292
assert creds # convince pytype this isn't None
293
self._loaded_cred_name = self._cred_name
294
args = [
295
self._cmd,
296
'--http2-proxy',
297
f'--frontend=*,{self._port}',
298
f'--backend=127.0.0.1,{self.env.proxy_port}',
299
'--log-level=ERROR',
300
f'--pid-file={self._pid_file}',
301
f'--errorlog-file={self._error_log}',
302
f'--conf={self._conf_file}',
303
f'--cacert={self.env.ca.cert_file}',
304
creds.pkey_file,
305
creds.cert_file,
306
]
307
ngerr = open(self._stderr, 'a')
308
self._process = subprocess.Popen(args=args, stderr=ngerr)
309
if self._process.returncode is not None:
310
return False
311
return not wait_live or self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT))
312
313
def wait_dead(self, timeout: timedelta):
314
curl = CurlClient(env=self.env, run_dir=self._tmp_dir)
315
try_until = datetime.now() + timeout
316
while datetime.now() < try_until:
317
check_url = f'https://{self.env.proxy_domain}:{self._port}/'
318
r = curl.http_get(url=check_url)
319
if r.exit_code != 0:
320
return True
321
log.debug(f'waiting for nghttpx-fwd to stop responding: {r}')
322
time.sleep(.1)
323
log.debug(f"Server still responding after {timeout}")
324
return False
325
326
def wait_live(self, timeout: timedelta):
327
curl = CurlClient(env=self.env, run_dir=self._tmp_dir)
328
try_until = datetime.now() + timeout
329
while datetime.now() < try_until:
330
check_url = f'https://{self.env.proxy_domain}:{self._port}/'
331
r = curl.http_get(url=check_url, extra_args=[
332
'--trace', 'curl.trace', '--trace-time'
333
])
334
if r.exit_code == 0:
335
return True
336
time.sleep(.1)
337
log.error(f"Server still not responding after {timeout}")
338
return False
339
340