kit-sub: Connection: keep-alive и ALPN http/1.1, как у nginx

На ответах, после которых соединение остаётся открытым, – «Connection: keep-alive»,
перед закрытием – «Connection: close» (раньше заголовка не было вовсе). На своём TLS
(--multi-port) kit-sub соглашается на ALPN http/1.1, как nginx без http2; клиент,
предложивший только h2, получает соединение без ALPN. Тесты – в kitsub_http_test.py.
This commit is contained in:
maxdmitrov
2026-10-06 17:03:57 +03:00
parent 220d82c01e
commit f7a8475c88
3 changed files with 57 additions and 4 deletions
+48 -1
View File
@@ -1,5 +1,5 @@
"""Ответы kit-sub как у nginx (режим --multi-port): python3 tools/test/kitsub_http_test.py"""
import json, os, socket, tempfile, threading, unittest, importlib.util
import json, os, shutil, socket, ssl, subprocess, sys, tempfile, threading, time, unittest, importlib.util
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
tmp = tempfile.mkdtemp()
@@ -58,6 +58,53 @@ class Http(unittest.TestCase):
r = raw(b"GET / HTTP/9.9\r\n\r\n")
self.assertTrue(r.startswith("HTTP/1.1 505"), r[:40])
def test_connection_header(self):
r = raw(b"GET / HTTP/1.1\r\nHost: x\r\n\r\nGET /a HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n")
first, second = r.split("HTTP/1.1 404", 2)[1:]
self.assertIn("\r\nConnection: keep-alive\r\n", first)
self.assertIn("\r\nConnection: close\r\n", second)
self.assertEqual(r.count("Connection:"), 2)
def test_http10_closes(self):
r = raw(b"GET / HTTP/1.0\r\n\r\n")
self.assertTrue(r.startswith("HTTP/1.1 404"))
self.assertIn("\r\nConnection: close\r\n", r)
self.assertNotIn("<open>", r)
@unittest.skipUnless(shutil.which("openssl"), "нужен openssl")
class Tls(unittest.TestCase):
"""Свой TLS (--multi-port): ALPN http/1.1, как у nginx без http2."""
def test_alpn(self):
d = tempfile.mkdtemp()
cert, key = os.path.join(d, "c.pem"), os.path.join(d, "k.pem")
subprocess.run(["openssl", "req", "-x509", "-nodes", "-newkey", "ec", "-pkeyopt", "ec_paramgen_curve:prime256v1",
"-keyout", key, "-out", cert, "-subj", "/CN=1.2.3.4", "-days", "1"], check=True, capture_output=True)
s = socket.socket(); s.bind(("127.0.0.1", 0)); port = s.getsockname()[1]; s.close()
cfg = os.path.join(d, "config.json")
with open(cfg, "w") as f:
json.dump({"listen": "127.0.0.1", "port": port, "path": "/sub/", "upstream": "http://127.0.0.1:1/", "cert": cert, "key": key}, f)
env = dict(os.environ, KIT_SUB_CONFIG=cfg, KIT_SUB_RULES=os.path.join(d, "rules.yaml"))
env.pop("CREDENTIALS_DIRECTORY", None)
p = subprocess.Popen([sys.executable, os.path.join(root, "scripts", "kit-sub.py")], env=env,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try:
ctx = ssl.create_default_context(); ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE
for alpn, want in ((["h2", "http/1.1"], "http/1.1"), (["h2"], None)):
ctx.set_alpn_protocols(alpn)
for _ in range(50):
try:
c = socket.create_connection(("127.0.0.1", port), timeout=3)
break
except OSError:
time.sleep(0.1)
with ctx.wrap_socket(c) as t:
self.assertEqual(t.selected_alpn_protocol(), want)
t.sendall(b"GET / HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n")
self.assertTrue(t.recv(64).startswith(b"HTTP/1.1 404"))
finally:
p.terminate(); p.wait()
if __name__ == "__main__":
unittest.main()