A Python port of the Invisible Internet Project (I2P)
1"""Minimal HTTP health endpoint using raw asyncio.
2
3No external dependencies — just a bare TCP server that speaks enough
4HTTP to return JSON status. Runs on a separate port from the NTCP2
5transport (default 9701).
6"""
7
8from __future__ import annotations
9
10import asyncio
11import json
12import logging
13
14logger = logging.getLogger(__name__)
15
16
17async def start_health_server(
18 get_status_fn,
19 host: str = "0.0.0.0",
20 port: int = 9701,
21) -> asyncio.Server:
22 """Start a minimal HTTP health server.
23
24 Parameters
25 ----------
26 get_status_fn:
27 Callable that returns a dict with router status.
28 host:
29 Bind address.
30 port:
31 Listen port (default 9701).
32
33 Returns
34 -------
35 asyncio.Server
36 The running server instance.
37 """
38
39 async def handle_request(
40 reader: asyncio.StreamReader,
41 writer: asyncio.StreamWriter,
42 ) -> None:
43 try:
44 # Read the request line (we don't care about the path)
45 await asyncio.wait_for(reader.readline(), timeout=5.0)
46 # Drain remaining headers
47 while True:
48 line = await asyncio.wait_for(reader.readline(), timeout=5.0)
49 if line in (b"\r\n", b"\n", b""):
50 break
51
52 status = get_status_fn()
53 body = json.dumps(status, indent=2).encode()
54 response = (
55 b"HTTP/1.1 200 OK\r\n"
56 b"Content-Type: application/json\r\n"
57 b"Connection: close\r\n"
58 b"Content-Length: " + str(len(body)).encode() + b"\r\n"
59 b"\r\n" + body
60 )
61 writer.write(response)
62 await writer.drain()
63 except Exception:
64 pass
65 finally:
66 writer.close()
67 try:
68 await writer.wait_closed()
69 except Exception:
70 pass
71
72 server = await asyncio.start_server(handle_request, host, port)
73 logger.info("Health server started on %s:%d", host, port)
74 return server