Source code for gatenet.http_.server
import json
from http.server import HTTPServer, BaseHTTPRequestHandler
import threading
from typing import Optional
from gatenet.core import Hooks, events
[docs]
class HTTPServerComponent:
"""
Simple HTTP server component for serving JSON responses.
- Binds to the given host and port.
- Uses Python's built-in HTTP server.
- Runs in a background thread via `start()`.
- Supports dynamic route registration via the `route` decorator.
Example
-------
>>> from gatenet.http_.server import HTTPServerComponent
>>> server = HTTPServerComponent(host="127.0.0.1", port=8080)
>>> @server.route("/status", method="GET")
... def status_handler(req):
... return {"ok": True}
>>> server.start()
# Now visit http://127.0.0.1:8080/status
"""
def __init__(self, host: str = "127.0.0.1", port: int = 8000, hooks: Optional[Hooks] = None):
"""
Initialize the HTTP server.
Parameters
----------
host : str, optional
Host address to bind to (default is "127.0.0.1").
port : int, optional
Port number to bind to (default is 8000).
"""
self.host = host
self.port = port
self._routes = {} # (path, method) -> handler func
self._thread = None
self.hooks = hooks or Hooks()
self.server = HTTPServer((self.host, self.port), self._make_handler())
def _make_handler(self):
routes = self._routes # Closure to capture routes
hooks = self.hooks
class RouteHTTPRequestHandler(BaseHTTPRequestHandler):
routes = {}
def do_GET(self):
self._handle("GET")
def do_POST(self):
self._handle("POST")
def do_PUT(self):
self._handle("PUT")
def do_DELETE(self):
self._handle("DELETE")
def do_PATCH(self):
self._handle("PATCH")
def _handle(self, method):
# Before-request hook
try:
hooks.emit(events.HTTP_BEFORE_REQUEST, req=self)
except Exception:
pass
handler = routes.get((self.path, method))
if handler:
try:
result = handler(self)
status = 200
body = json.dumps(result).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(body)
# After-response hook
try:
hooks.emit(
events.HTTP_AFTER_RESPONSE,
req=self,
status=status,
headers={"Content-Type": "application/json"},
body=body,
)
except Exception:
pass
except Exception as e:
# Exception hook
try:
hooks.emit(events.HTTP_EXCEPTION, req=self, exc=e)
except Exception:
pass
self.send_response(500)
self.end_headers()
self.wfile.write(b"Internal Server Error \n")
self.wfile.write(str(e).encode())
else:
# Route-not-found hook
try:
hooks.emit(events.HTTP_ROUTE_NOT_FOUND, path=self.path, method=method)
except Exception:
pass
self.send_response(404)
self.end_headers()
self.wfile.write(json.dumps({ "code": 404, "error": "Not Found" }).encode())
return RouteHTTPRequestHandler
[docs]
def route(self, path: str, method: str = "GET"):
"""
Decorator to register a route handler.
:param path: The path to register the handler for.
:param method: The HTTP method (GET, POST, etc.).
"""
def decorator(func):
self._routes[(path, method.upper())] = func
return func
return decorator
[docs]
def start(self):
"""
Start the server in a background thread.
"""
def run():
try:
self.server.serve_forever()
except Exception as e:
print(f"[HTTP] Server error: {e}")
self._thread = threading.Thread(target=run, daemon=True)
self._thread.start()
[docs]
def stop(self):
"""
Stop the server.
"""
if self.server:
self.server.shutdown()
self.server.server_close()