kit-sub: ответы по HTTP/1.1, как у nginx
В режиме --multi-port kit-sub отвечал строкой статуса HTTP/1.0 и «Server: nginx » с пробелом. Теперь protocol_version = HTTP/1.1, заголовок Server без хвоста; на ответы, после которых соединение закрывается (ошибки разбора, POST и т. п.), добавлен Connection: close. Тест: tools/test/kitsub_http_test.py.
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
* Раздельная маршрутизация: `kit net split on` – и через VPN идёт только список популярных иностранных сервисов (нейросети, видео, соцсети, мессенджеры, GitHub, Google и другие), а всё остальное напрямую. Правила приезжают вместе с подпиской, домашним ничего настраивать не нужно: приложения на Mihomo (Clash Verge, FlClash, Mihomo Party) берут их из подписки, Happ – заголовком, приложения на JSON-подписке Xray – из конфига. DNS не течёт, проверено клиентами Mihomo и Xray. Список в `/etc/kit-sub/rules.yaml`, проверка по базам категорий `kit net split check`, после правки `kit net split apply`, он входит в `kit backup`. Идея и первый набросок – [VasilevVitalii](https://github.com/VasilevVitalii), спасибо. У v2rayN, Karing, Hiddify и приложений с обычным списком ссылок подписка прежняя.
|
||||
* Исправил `--restore` на сервер с другим IP: ссылки части протоколов (адреса в таблице `hosts` панели и в настройках kit-sub) оставались со старым IP, а проверка панели после запуска могла ложно сообщить «Панель не отвечает», если панель стартует не сразу. А файлы настроек из копии `--restore` теперь читает строго по списку, как охранник на входе: лишнее имя – и копия не принимается, ничего не меняется.
|
||||
* Исправил `kit backup`: из `/root` он падал с «No such file or directory», работал только из `/`. Теперь работает откуда угодно. Если токен API панели в `/etc/x-ui/install-result.env` не подошёл (так бывает после обновления с 1.0), `kit` сам выпускает новый и сохраняет его, а если панель всё же не отвечает – говорит по-человечески (код ответа и вероятная причина), а не «Unsupported HTTP version in response».
|
||||
* `kit-sub` на своём порту (`--multi-port`) отвечает по HTTP/1.1 и с `Server: nginx` без лишнего пробела – как настоящий nginx, а не его двоюродный брат.
|
||||
* JSON-подписка снова открывается в приложениях на Xray: там, где у vless не было `encryption`, подписка теперь дописывает `none`.
|
||||
|
||||
Сервер обновится сам этой ночью, нетерпеливым – `kit update`.
|
||||
|
||||
@@ -338,8 +338,12 @@ def apply_rules(clash_yaml, loaded):
|
||||
class Handler(http.server.BaseHTTPRequestHandler):
|
||||
server_version = "nginx"
|
||||
sys_version = ""
|
||||
protocol_version = "HTTP/1.1" # как у nginx; ответы всегда с Content-Length
|
||||
timeout = 20 # зависшие соединения не держим
|
||||
|
||||
def version_string(self): # «Server: nginx» без хвостового пробела
|
||||
return self.server_version
|
||||
|
||||
def setup(self):
|
||||
# TLS-рукопожатие – в потоке запроса, а не в общем цикле приёма соединений.
|
||||
# Без сертификата (за nginx, на 127.0.0.1) работаем по обычному HTTP.
|
||||
@@ -378,11 +382,15 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", "text/html")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
if self.close_connection:
|
||||
self.send_header("Connection", "close")
|
||||
self.end_headers()
|
||||
if self.command != "HEAD":
|
||||
self.wfile.write(body)
|
||||
|
||||
def do_other(self):
|
||||
# Тело запроса не читаем – значит, и соединение дальше не держим.
|
||||
self.close_connection = True
|
||||
self.send_html_error(404)
|
||||
|
||||
do_POST = do_PUT = do_DELETE = do_PATCH = do_OPTIONS = do_other
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"""Ответы kit-sub как у nginx (режим --multi-port): python3 tools/test/kitsub_http_test.py"""
|
||||
import json, os, socket, tempfile, threading, unittest, importlib.util
|
||||
|
||||
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
tmp = tempfile.mkdtemp()
|
||||
cfgp = os.path.join(tmp, "config.json")
|
||||
json.dump({"listen": "127.0.0.1", "port": 1, "path": "/sub/", "upstream": "http://127.0.0.1:1/"}, open(cfgp, "w"))
|
||||
os.environ["KIT_SUB_CONFIG"] = cfgp
|
||||
os.environ["KIT_SUB_RULES"] = os.path.join(tmp, "rules.yaml")
|
||||
spec = importlib.util.spec_from_file_location("kit_sub", os.path.join(root, "scripts", "kit-sub.py"))
|
||||
ks = importlib.util.module_from_spec(spec); spec.loader.exec_module(ks)
|
||||
srv = ks.Server(("127.0.0.1", 0), ks.Handler)
|
||||
threading.Thread(target=srv.serve_forever, daemon=True).start()
|
||||
|
||||
|
||||
def raw(data):
|
||||
s = socket.create_connection(srv.server_address[:2]); s.settimeout(3); s.sendall(data); out = b""
|
||||
try:
|
||||
while True:
|
||||
b = s.recv(65536)
|
||||
if not b:
|
||||
break
|
||||
out += b
|
||||
except socket.timeout:
|
||||
out += b"<open>"
|
||||
s.close()
|
||||
return out.decode(errors="replace")
|
||||
|
||||
|
||||
class Http(unittest.TestCase):
|
||||
def test_status_and_server(self):
|
||||
r = raw(b"GET / HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n")
|
||||
self.assertTrue(r.startswith("HTTP/1.1 404 Not Found\r\n"), r[:40])
|
||||
self.assertIn("\r\nServer: nginx\r\n", r)
|
||||
self.assertIn("\r\nContent-Length: ", r)
|
||||
|
||||
def test_keep_alive(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")
|
||||
self.assertEqual(r.count("HTTP/1.1 404 Not Found"), 2)
|
||||
self.assertNotIn("<open>", r)
|
||||
|
||||
def test_post_closes(self):
|
||||
r = raw(b"POST / HTTP/1.1\r\nHost: x\r\nContent-Length: 5\r\n\r\nhello")
|
||||
self.assertTrue(r.startswith("HTTP/1.1 404"))
|
||||
self.assertIn("Connection: close", r)
|
||||
self.assertNotIn("<open>", r)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user