MenuFastAPI

Proxy guides

FastAPI

Serve the ProductLense script from a FastAPI app and forward events asynchronously with httpx, keeping visitor counts and countries correct.

Two async routes serve the script from your domain and, on the Scale plan, forward events. Read the proxy overview first for what each one fixes.

The routes

pl_proxy.py
import json
import os
import time

import httpx
from fastapi import APIRouter, Request, Response

router = APIRouter()
client = httpx.AsyncClient(timeout=5)
_script = {"body": b"", "at": 0.0}


@router.get("/js/a.js")
async def script() -> Response:
    if time.time() - _script["at"] > 3600:
        r = await client.get("https://api.productlense.app/a.js")
        if r.is_success:
            _script.update(body=r.content, at=time.time())
    if not _script["body"]:
        return Response(status_code=502)
    return Response(_script["body"], media_type="application/javascript",
                    headers={"Cache-Control": "public, max-age=86400"})


@router.post("/m/api/e")
async def events(request: Request) -> Response:
    raw = await request.body()
    if len(raw) > 8192:
        return Response(status_code=413)
    try:
        body = json.loads(raw)
    except ValueError:
        return Response(status_code=400)
    if not isinstance(body, dict):
        return Response(status_code=400)
    extra = {"ip": request.client.host if request.client else "", "ua": request.headers.get("user-agent", "")}
    if isinstance(body.get("b"), list):
        body["b"] = [{**e, **extra} for e in body["b"] if isinstance(e, dict)]
    else:
        body.update(extra)
    r = await client.post("https://api.productlense.app/api/v1/events", json=body,
                          headers={"Authorization": f"Bearer {os.environ['PL_API_KEY']}"})
    return Response(status_code=r.status_code)
main.py
from fastapi import FastAPI

from pl_proxy import router as productlense

app = FastAPI()
app.include_router(productlense)

The route reads the raw body because the browser sends it as text/plain. Behind a reverse proxy, run Uvicorn with --proxy-headers --forwarded-allow-ips=<proxy ip> so request.client.host is the visitor's address and not the proxy's.

Use it

Serve only the script on any plan:

HTML
<script defer data-site="SITE_ID" data-api="https://api.productlense.app" src="/js/a.js"></script>

On the Scale plan, set PL_API_KEY to a key with the events scope and change the tag to data-api="https://example.com/m".

Check it works

  1. Open https://example.com/js/a.js: it returns JavaScript.
  2. Load a page with the developer tools open. The Network tab shows a POST to api/e with status 204.
  3. Open the Live page in the ProductLense dashboard. Your visit appears within a few seconds.

    ↑ ↓ to move, Enter to open