fix: HAProxy batch commands and improve routing/subdomain handling

- Fix haproxy_cmd_batch to send each command on separate connection
  (HAProxy Runtime API only processes first command on single connection)
- HTTP frontend now routes to backends instead of redirecting to HTTPS
- Add subdomain detection to avoid duplicate wildcard entries
- Add reload verification with retry logic
- Optimize SSL: TLS 1.3 ciphersuites, extended session lifetime
- Add CPU steal monitoring script

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kaffa
2026-02-03 00:55:24 +09:00
parent 95aecccb03
commit 46c86b62f2
5 changed files with 81 additions and 149 deletions

View File

@@ -106,10 +106,11 @@ def _check_response_for_errors(response: str) -> None:
def haproxy_cmd_batch(commands: list[str]) -> list[str]:
"""Send multiple commands to HAProxy in a single connection.
"""Send multiple commands to HAProxy.
This is more efficient than multiple haproxy_cmd calls as it reuses
the same TCP connection for all commands.
Note: HAProxy Runtime API only processes the first command when multiple
commands are sent on a single connection. This function sends each command
on a separate connection to ensure all commands are executed.
Args:
commands: List of HAProxy commands to execute
@@ -126,64 +127,16 @@ def haproxy_cmd_batch(commands: list[str]) -> list[str]:
if len(commands) == 1:
return [haproxy_cmd_checked(commands[0])]
# Send all commands separated by newlines
combined = "\n".join(commands)
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(SOCKET_TIMEOUT)
s.connect(HAPROXY_SOCKET)
s.sendall(f"{combined}\n".encode())
s.shutdown(socket.SHUT_WR)
# Send each command on separate connection (HAProxy limitation)
responses = []
for cmd in commands:
try:
resp = haproxy_cmd_checked(cmd)
responses.append(resp)
except HaproxyError:
raise
# Set socket to non-blocking for select-based recv loop
s.setblocking(False)
response = b""
start_time = time.time()
while True:
elapsed = time.time() - start_time
if elapsed >= SOCKET_RECV_TIMEOUT:
raise HaproxyError(f"Response timeout after {SOCKET_RECV_TIMEOUT} seconds")
remaining = SOCKET_RECV_TIMEOUT - elapsed
ready, _, _ = select.select([s], [], [], min(remaining, 1.0))
if ready:
data = s.recv(8192)
if not data:
break
response += data
if len(response) > MAX_RESPONSE_SIZE:
raise HaproxyError(f"Response exceeded {MAX_RESPONSE_SIZE} bytes limit")
full_response = response.decode().strip()
# Split responses - HAProxy separates responses with empty lines
# For commands that return nothing, we get empty strings
responses = full_response.split("\n\n") if full_response else [""] * len(commands)
# If we got fewer responses than commands, pad with empty strings
while len(responses) < len(commands):
responses.append("")
# Check each response for errors
for i, resp in enumerate(responses):
resp = resp.strip()
_check_response_for_errors(resp)
responses[i] = resp
return responses
except socket.timeout:
raise HaproxyError("Connection timeout")
except ConnectionRefusedError:
raise HaproxyError("Connection refused - HAProxy not running?")
except UnicodeDecodeError:
raise HaproxyError("Invalid UTF-8 in response")
except HaproxyError:
raise
except Exception as e:
raise HaproxyError(str(e)) from e
return responses
def reload_haproxy() -> tuple[bool, str]: