import asyncio
import base64
import urllib.error
import urllib.request
from boxlite import SimpleBox
HOST_PORT = 8080
GUEST_PORT = 8080
# The service to run inside the sandbox. A zero-dependency JSON API keeps the
# example self-contained; substitute your generated Flask/FastAPI app here.
APP_CODE = f'''
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def _json(self, code, payload):
body = json.dumps(payload).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
if self.path == "/api/health":
self._json(200, {{"status": "ok"}})
elif self.path.startswith("/api/preview"):
self._json(200, {{"html": "<h1>Hello from the sandbox</h1>"}})
else:
self._json(404, {{"error": "not found"}})
def log_message(self, *args): # silence the default access log
pass
# Must bind 0.0.0.0 — see Trust and limits
HTTPServer(("0.0.0.0", {GUEST_PORT}), Handler).serve_forever()
'''
async def main() -> None:
try:
# 1) Declare the port mapping when the box is created
async with SimpleBox(
image="python:alpine",
name="webapp-preview",
ports=[(HOST_PORT, GUEST_PORT)],
reuse_existing=True, # reuse a box of the same name across previews
) as box:
print(f"box started: {box.id}")
# 2) Write the app into the box (base64 avoids quoting issues)
encoded = base64.b64encode(APP_CODE.encode()).decode()
await box.exec("sh", "-c", "mkdir -p /app")
await box.exec("sh", "-c", f"echo {encoded} | base64 -d > /app/server.py")
# 3) Start it in the background so it outlives the exec call
await box.exec("sh", "-c", "nohup python /app/server.py > /tmp/app.log 2>&1 &")
await asyncio.sleep(2) # give the server a moment to bind
# 4) Call the forwarded endpoints from the host
for path in ("/api/health", "/api/preview?id=1", "/api/missing"):
url = f"http://127.0.0.1:{HOST_PORT}{path}"
try:
with urllib.request.urlopen(url, timeout=5) as response:
print(f"GET {path} -> {response.status} {response.read().decode()}")
except urllib.error.HTTPError as exc:
# A 404 from the app is a valid response, not a transport failure
print(f"GET {path} -> {exc.code} {exc.read().decode()}")
except OSError as exc:
print(f"GET {path} failed: {exc!r}")
except RuntimeError as exc:
print(f"sandbox failed to start: {exc}")
asyncio.run(main())