3.2 KiB
3.2 KiB
Browser Gateway Pattern
Built 2026-07-29 for gorouter.app. Use when API keys are broken but browser sessions work.
Problem
API service returns 401 "Invalid token" for all API keys (even freshly created ones), but the browser playground works fine with session cookies. Direct HTTP requests with cookies get blocked by Cloudflare (CF 1010). Need a working API endpoint.
Solution: Playwright-Based API Proxy
Run a long-lived Playwright browser with an authenticated session. Route incoming API requests through page.evaluate() → fetch() — the request originates from the browser context, bypassing Cloudflare.
Architecture
Client (curl) → localhost:8777/v1/chat/completions
↓
aiohttp handler picks an active Playwright page
↓
page.evaluate(fetch(gourouter.app/v1/chat/completions))
↓
response returned to client
Core Code
async def api_request(self, page, method, path, body=None):
"""Make API request through browser context"""
js_code = f"""
(async () => {{
const headers = {{ 'Content-Type': 'application/json' }};
const opts = {{ method: '{method}', headers }};
if (body) opts.body = JSON.stringify(body);
const resp = await fetch('https://gorouter.app{path}', opts);
const text = await resp.text();
return JSON.stringify({{
status: resp.status,
body: text,
headers: Object.fromEntries(resp.headers.entries()),
}});
}})()
"""
for attempt in range(3):
try:
result = await page.evaluate(js_code)
return json.loads(result)
except Exception as e:
if attempt < 2:
await asyncio.sleep(2)
return {"status": 0, "body": "all attempts failed"}
Session Management
- One
BrowserContextper authenticated user - Login via GitHub OAuth + 2FA TOTP (same flow as auto-reg)
- Check liveness with
await page.evaluate("1")before each request - Remove dead contexts on failure
Key Design Decisions
- Playwright, not raw HTTP: Cloudflare blocks non-browser User-Agents.
page.evaluate()runs in the real browser context. - aiohttp for the proxy server: Lightweight async HTTP. One browser instance, multiple routes.
- Session pool, not one-session-per-request: Login is slow (~60s). Keep sessions alive and reuse.
- 3 retries on fetch failures: Browser internals can flake; retry before giving up.
Pitfalls
- Browser sessions expire: Gorouter cookies last ~30 min. Gateway needs periodic re-login.
- One browser = one IP: All proxied requests share the same browser fingerprint and IP. Rate limits apply.
- Memory: Each Playwright page consumes ~50-100MB. Pool size is limited.
- Not for production: This is a workaround for broken API keys. Replace with real API keys when the service fixes auth.
Full Reference Implementation
See gorouter_gateway.py for the complete implementation with:
/loginendpoint to add sessions/v1/chat/completions— OpenAI-compatible/v1/models— model list/health— session status