- pin kaya-core/kaya-rsgi >= 0.0.2 (ctx.client now honors Forwarded and X-Forwarded-* headers); adapt route tests and add explicit coverage - add rloop dependency and set GRANIAN_LOOP=rloop in the Docker image so granian serves the app on the Rust event loop instead of asyncio - regenerate requirements.txt (kaya 0.0.2, rloop 0.5.0) - document the new loop and forwarded-header behavior in the README
4.2 KiB
pyfconfig
A clone of ifconfig.me built on the
kaya framework
(kaya-core + kaya-rsgi), served by Granian
over the RSGI protocol.
Stack
- kaya-core — routing and HTTP request/response handling
- kaya-rsgi — Granian (RSGI) adapter
- granian — application server
- rloop — Rust event loop used by Granian instead of the stdlib asyncio loop
- httpx + pwo — test client over kaya's ASGI transport
No database, sessions, or authentication — the app is stateless.
Endpoints
| Method | Path | Response |
|---|---|---|
| GET | / |
HTML page for browsers (Accept: text/html), plain-text IP otherwise |
| GET | /ip |
Client IP address |
| GET | /ua |
User-Agent header |
| GET | /lang |
Accept-Language header |
| GET | /encoding |
Accept-Encoding header |
| GET | /mime |
Accept header |
| GET | /charset |
Accept-Charset header |
| GET | /forwarded |
X-Forwarded-For header |
| GET | /all |
All connection fields as key: value lines (empty fields included; remote_host shown as unavailable) |
| GET | /all.json |
Same fields as a JSON object, empty fields omitted |
| GET | /api/health |
Liveness probe ({"status":"ok"}) |
The /all field order mirrors the reference site: ip_addr,
remote_host, user_agent, port, language, referer, connection,
keep_alive, method, encoding, mime, charset, via, forwarded.
The reported client IP/port honor the Forwarded, X-Forwarded-For,
X-Forwarded-Host and X-Forwarded-Port proxy headers (kaya ≥ 0.0.2);
without them the socket peer address is used.
Example:
$ curl http://localhost:8080/
203.0.113.7
$ curl http://localhost:8080/all.json
{"ip_addr":"203.0.113.7","user_agent":"curl/8.21.0","port":"51342","method":"GET","mime":"*/*"}
Running
With Docker Compose (recommended)
docker compose up -d --build
# App published at http://localhost:8080
On the host
cp .env.example .env # optional; all settings have defaults
python -m venv .venv && . .venv/bin/activate
pip install --index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple \
--extra-index-url https://pypi.org/simple \
-e .
granian --interface rsgi --loop rloop --host 0.0.0.0 --port 8000 pyfconfig.app:app
The --loop rloop flag (or GRANIAN_LOOP=rloop, already set in the
Docker image) makes Granian run the app on the
rloop Rust event loop instead of the
stdlib asyncio loop.
Configuration
Environment variables (see .env.example):
| Variable | Default | Description |
|---|---|---|
SITE_NAME |
pyfconfig |
Public name used in the HTML page title and the curl examples (set to your domain, e.g. ifconfig.example.com) |
The bind address is configured through Granian itself (GRANIAN_HOST /
GRANIAN_PORT env vars or --host / --port CLI flags).
Tests
. .venv/bin/activate
python -m unittest discover -s tests -t .
The tests drive the app in-process through kaya's ASGI interface using
httpx.ASGITransport; no external services are needed.
Project layout
src/pyfconfig/
├── app.py # KayaApp assembly; imports route modules
├── config.py # env-driven Settings dataclass
├── page.py # HTML landing page builder (kaya has no templating)
└── routes/
├── health.py # GET /api/health
├── root.py # GET / with content negotiation
└── connection.py # /ip /ua /lang /encoding /mime /charset /forwarded /all /all.json
tests/
└── test_routes.py # unittest + httpx.ASGITransport
Route modules import the module-level app from pyfconfig.app and
register handlers with @app.GET(...) decorators at import time —
pyfconfig.app imports them last to complete the wiring.
Deployment
The Dockerfile is a multi-stage alpine build producing a minimal,
non-root runtime image; pushing a release/* tag triggers the Gitea
Actions workflow in .gitea/workflows/build.yaml, which builds and
publishes the image to gitea.woggioni.net/woggioni-opencode-agent/pyfconfig.