mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-15 16:15:49 +08:00
The reload asked for through /R happens on the next tick of the main
loop, and between the old services stopping and the new ones starting a
request is refused: on Windows the new listener cannot bind the port
until the old one has closed it, and retries with a growing delay. When
the tick fell inside the case, every request after it failed with
ConnectionRefusedError (MSVC run of 98e8dfb). The case now waits for
both services to have restarted after each /R.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 lines
3.5 KiB
Python
90 lines
3.5 KiB
Python
"""The admin interface, now a set of handlers on the HTTP server."""
|
|
|
|
import time
|
|
|
|
|
|
def run(t):
|
|
adm = t.free_port()
|
|
lim = t.free_port()
|
|
server = t.start("admin", f"""
|
|
log
|
|
auth iponly
|
|
allow *
|
|
countin 1 D 100 * * *
|
|
countin 2 D 200 * * *
|
|
admin -p{adm}
|
|
|
|
flush
|
|
auth iponly
|
|
allow *
|
|
admin -p{lim} -s1
|
|
""", ports=[adm, lim])
|
|
|
|
url = f"http://127.0.0.1:{adm}"
|
|
|
|
def reload(path):
|
|
"""Ask for a reload and wait for it to be done. It happens on the next
|
|
tick of the main loop, at most a second later; both old services stop
|
|
and the new ones start, and a request in between is refused (Windows
|
|
cannot bind a port still listening) or reset."""
|
|
before = server.output()
|
|
accepting = before.count("Accepting connections")
|
|
exiting = before.count("Exiting thread")
|
|
reply = t.http(url + path)
|
|
deadline = time.time() + 10
|
|
while time.time() < deadline:
|
|
now = server.output()
|
|
if (now.count("Accepting connections") >= accepting + 2
|
|
and now.count("Exiting thread") >= exiting + 2):
|
|
break
|
|
time.sleep(0.05)
|
|
return reply
|
|
|
|
# --- the predefined pages -----------------------------------------
|
|
t.eq(200, t.http(url + "/").status, "the main page")
|
|
t.eq(200, t.http(url + "/C").status, "the counters page")
|
|
t.eq(200, reload("/R").status, "the reload page")
|
|
t.eq(200, t.http(url + "/S").status, "the services page")
|
|
|
|
counters = t.http(url + "/C")
|
|
t.contains(counters, "countin", "the counters page names the counter type")
|
|
t.contains(counters, "<tr>", "the counters page renders a table")
|
|
t.contains(reload("/R"), "Reload", "the reload page confirms the request")
|
|
t.contains(t.http(url + "/S"), "<", "the services page returns markup")
|
|
|
|
# --- the menu no longer offers the removed config editor -----------
|
|
main = t.http(url + "/")
|
|
t.contains(main, "HREF='/C'", "the menu links to the counters")
|
|
t.contains(main, "HREF='/R'", "the menu links to reload")
|
|
t.contains(main, "HREF='/S'", "the menu links to the services")
|
|
t.not_contains(main, "HREF='/F'",
|
|
"the menu no longer links to the config editor")
|
|
|
|
# /F and /U are gone, so they fall through to the catch-all rule
|
|
t.eq(200, t.http(url + "/F").status, "the removed /F falls through")
|
|
t.eq(200, t.http(url + "/U").status, "the removed /U falls through")
|
|
t.contains(t.http(url + "/F"), "configuration", "/F yields the main page")
|
|
|
|
# --- counter control through the glob ------------------------------
|
|
# /C<action><number> is routed by the /C* rule, the action arriving as
|
|
# the glob
|
|
t.http(url + "/CD0")
|
|
t.contains(t.http(url + "/C"), ">NO<", "a counter can be disabled")
|
|
t.http(url + "/CS0")
|
|
t.contains(t.http(url + "/C"), ">YES<", "a counter can be enabled again")
|
|
|
|
# --- limited mode ---------------------------------------------------
|
|
limited = f"http://127.0.0.1:{lim}"
|
|
t.eq(200, t.http(limited + "/").status, "limited mode serves the main page")
|
|
t.eq(200, t.http(limited + "/C").status, "limited mode serves the counters")
|
|
t.not_contains(t.http(limited + "/R"), "Reload scheduled",
|
|
"limited mode refuses a reload")
|
|
|
|
# --- the writable command is gone ------------------------------------
|
|
output = t.run_config("writable", f"""
|
|
log
|
|
writable
|
|
admin -p{t.free_port()}
|
|
""")
|
|
t.contains(output, "Unknown command", "the writable command is rejected")
|