ported everything to Reticulum mesh
Replace HTTP server with Reticulum-native architecture. The server now speaks only Reticulum, with a client-side gateway providing browser access by translating HTTP to/from RNS requests. - Extract db layer (db.py), templates (templates.py), handlers (handlers.py) - app.py is now the RNS server with persistent identity and destination - gateway.py bridges HTTP on localhost:8080 to RNS link requests - Add rns dependency, add .gitignore
This commit is contained in:
parent
7caafd665c
commit
4b4e7e8081
7 changed files with 1027 additions and 511 deletions
149
gateway.py
Normal file
149
gateway.py
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
import sys
|
||||
import time
|
||||
import threading
|
||||
import RNS
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
APP_NAME = "tinyweb"
|
||||
ASPECTS = ["server"]
|
||||
GATEWAY_PORT = 8080
|
||||
REQUEST_TIMEOUT = 60
|
||||
|
||||
|
||||
class GatewayState:
|
||||
reticulum = None
|
||||
destination = None
|
||||
link = None
|
||||
link_lock = threading.Lock()
|
||||
|
||||
|
||||
def resolve_destination(dest_hash_hex):
|
||||
dest_hash = bytes.fromhex(dest_hash_hex)
|
||||
|
||||
if not RNS.Transport.has_path(dest_hash):
|
||||
RNS.Transport.request_path(dest_hash)
|
||||
print(f"Requesting path to {RNS.prettyhexrep(dest_hash)}...")
|
||||
elapsed = 0
|
||||
while not RNS.Transport.has_path(dest_hash) and elapsed < 15:
|
||||
time.sleep(0.5)
|
||||
elapsed += 0.5
|
||||
if not RNS.Transport.has_path(dest_hash):
|
||||
raise ConnectionError(f"Could not find path to {RNS.prettyhexrep(dest_hash)}")
|
||||
|
||||
server_identity = RNS.Identity.recall(dest_hash)
|
||||
GatewayState.destination = RNS.Destination(
|
||||
server_identity,
|
||||
RNS.Destination.OUT,
|
||||
RNS.Destination.SINGLE,
|
||||
APP_NAME,
|
||||
*ASPECTS,
|
||||
)
|
||||
print(f"Resolved destination: {RNS.prettyhexrep(dest_hash)}")
|
||||
|
||||
|
||||
def ensure_link():
|
||||
with GatewayState.link_lock:
|
||||
if GatewayState.link and GatewayState.link.status == RNS.Link.ACTIVE:
|
||||
return GatewayState.link
|
||||
|
||||
print("Establishing link...")
|
||||
link = RNS.Link(GatewayState.destination)
|
||||
elapsed = 0
|
||||
while link.status == RNS.Link.PENDING and elapsed < 15:
|
||||
time.sleep(0.25)
|
||||
elapsed += 0.25
|
||||
|
||||
if link.status != RNS.Link.ACTIVE:
|
||||
raise ConnectionError("Link establishment failed")
|
||||
|
||||
GatewayState.link = link
|
||||
print("Link established")
|
||||
return link
|
||||
|
||||
|
||||
class GatewayHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def _forward(self, method):
|
||||
parsed = urlparse(self.path)
|
||||
query = parse_qs(parsed.query)
|
||||
|
||||
body = {}
|
||||
if method == "POST":
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
raw = self.rfile.read(length).decode()
|
||||
body = parse_qs(raw)
|
||||
|
||||
request_data = {
|
||||
"method": method,
|
||||
"path": parsed.path,
|
||||
"query": query,
|
||||
"body": body,
|
||||
"gateway_host": self.headers.get("Host", f"localhost:{GATEWAY_PORT}"),
|
||||
}
|
||||
|
||||
try:
|
||||
link = ensure_link()
|
||||
receipt = link.request(
|
||||
"/tinyweb",
|
||||
data=request_data,
|
||||
timeout=REQUEST_TIMEOUT,
|
||||
)
|
||||
|
||||
# Wait for the response
|
||||
elapsed = 0
|
||||
done_statuses = (RNS.RequestReceipt.READY, RNS.RequestReceipt.DELIVERED, RNS.RequestReceipt.FAILED)
|
||||
while receipt.get_status() not in done_statuses and elapsed < REQUEST_TIMEOUT:
|
||||
time.sleep(0.1)
|
||||
elapsed += 0.1
|
||||
|
||||
if receipt.get_status() in (RNS.RequestReceipt.READY, RNS.RequestReceipt.DELIVERED):
|
||||
resp = receipt.get_response()
|
||||
self.send_response(resp["status"])
|
||||
self.send_header("Content-Type", resp.get("content_type", "text/html; charset=utf-8"))
|
||||
for k, v in resp.get("headers", {}).items():
|
||||
self.send_header(k, v)
|
||||
self.end_headers()
|
||||
resp_body = resp.get("body", "")
|
||||
if resp_body:
|
||||
self.wfile.write(resp_body.encode() if isinstance(resp_body, str) else resp_body)
|
||||
elif receipt.get_status() == RNS.RequestReceipt.FAILED:
|
||||
self.send_error(504, "Request to TinyWeb server failed")
|
||||
else:
|
||||
self.send_error(504, "Request to TinyWeb server timed out")
|
||||
|
||||
except ConnectionError as e:
|
||||
GatewayState.link = None
|
||||
self.send_error(502, f"Gateway error: {e}")
|
||||
except Exception as e:
|
||||
GatewayState.link = None
|
||||
self.send_error(502, f"Gateway error: {e}")
|
||||
|
||||
def do_GET(self):
|
||||
self._forward("GET")
|
||||
|
||||
def do_POST(self):
|
||||
self._forward("POST")
|
||||
|
||||
def log_message(self, format, *args):
|
||||
print(f"[Gateway] {args[0]}")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: python gateway.py <destination_hash>")
|
||||
print(f" The destination hash is printed by app.py on startup.")
|
||||
sys.exit(1)
|
||||
|
||||
dest_hash = sys.argv[1].replace("<", "").replace(">", "")
|
||||
|
||||
GatewayState.reticulum = RNS.Reticulum()
|
||||
resolve_destination(dest_hash)
|
||||
|
||||
print(f"Gateway listening on http://localhost:{GATEWAY_PORT}")
|
||||
print(f"Open http://localhost:{GATEWAY_PORT} in your browser")
|
||||
HTTPServer(("127.0.0.1", GATEWAY_PORT), GatewayHandler).serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue