Upgrade to kaya 0.0.2 and run on the rloop event loop
CI / Build and push docker image (push) Successful in 1m39s
CI / Build and push docker image (push) Successful in 1m39s
- 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
This commit is contained in:
+2
-1
@@ -40,7 +40,8 @@ ENV PATH="/opt/venv/bin:$PATH" \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
GRANIAN_HOST=0.0.0.0 \
|
||||
GRANIAN_PORT=8080 \
|
||||
GRANIAN_INTERFACE=rsgi
|
||||
GRANIAN_INTERFACE=rsgi \
|
||||
GRANIAN_LOOP=rloop
|
||||
|
||||
USER app
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ over the RSGI protocol.
|
||||
- **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.
|
||||
@@ -34,6 +35,10 @@ 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:
|
||||
|
||||
```console
|
||||
@@ -60,9 +65,14 @@ 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 --host 0.0.0.0 --port 8000 pyfconfig.app:app
|
||||
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](https://github.com/gi0baro/rloop) Rust event loop instead of the
|
||||
stdlib asyncio loop.
|
||||
|
||||
### Configuration
|
||||
|
||||
Environment variables (see `.env.example`):
|
||||
|
||||
+3
-2
@@ -9,11 +9,12 @@ description = "A clone of https://ifconfig.me/ built on the kaya framework"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"kaya-core",
|
||||
"kaya-rsgi",
|
||||
"kaya-core>=0.0.2",
|
||||
"kaya-rsgi>=0.0.2",
|
||||
"granian>=2.0",
|
||||
"httpx",
|
||||
"pwo",
|
||||
"rloop>=0.5.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
+4
-2
@@ -29,17 +29,19 @@ idna==3.19
|
||||
# via
|
||||
# anyio
|
||||
# httpx
|
||||
kaya-core==0.0.1
|
||||
kaya-core==0.0.2
|
||||
# via
|
||||
# kaya-rsgi
|
||||
# pyfconfig (pyproject.toml)
|
||||
kaya-rsgi==0.0.1
|
||||
kaya-rsgi==0.0.2
|
||||
# via pyfconfig (pyproject.toml)
|
||||
pwo==0.1.2
|
||||
# via
|
||||
# kaya-core
|
||||
# kaya-rsgi
|
||||
# pyfconfig (pyproject.toml)
|
||||
rloop==0.5.0
|
||||
# via pyfconfig (pyproject.toml)
|
||||
typing-extensions==4.16.0
|
||||
# via
|
||||
# anyio
|
||||
|
||||
+12
-2
@@ -12,6 +12,10 @@ from pyfconfig.app import app
|
||||
# httpx's ASGITransport populates the scope with this client tuple.
|
||||
CLIENT_IP = "127.0.0.1"
|
||||
CLIENT_PORT = "123"
|
||||
# First entry of the X-Forwarded-For header in ALL_HEADERS; since kaya 0.0.2
|
||||
# ctx.client honors forwarded headers, ip_addr resolves to this instead of
|
||||
# the socket peer address.
|
||||
FORWARDED_IP = "203.0.113.7"
|
||||
|
||||
ALL_HEADERS = {
|
||||
"User-Agent": "test-agent/1.0",
|
||||
@@ -48,6 +52,12 @@ class RoutesTest(unittest.TestCase):
|
||||
self.assertEqual(200, r.status_code)
|
||||
self.assertEqual(CLIENT_IP, r.text.strip())
|
||||
|
||||
@async_test
|
||||
async def test_ip_honors_x_forwarded_for(self) -> None:
|
||||
async with self.client() as client:
|
||||
r = await client.get("/ip", headers={"X-Forwarded-For": "203.0.113.7"})
|
||||
self.assertEqual("203.0.113.7", r.text.strip())
|
||||
|
||||
@async_test
|
||||
async def test_ua(self) -> None:
|
||||
async with self.client() as client:
|
||||
@@ -106,7 +116,7 @@ class RoutesTest(unittest.TestCase):
|
||||
keys,
|
||||
)
|
||||
values = dict(line.split(": ", 1) for line in lines)
|
||||
self.assertEqual(CLIENT_IP, values["ip_addr"])
|
||||
self.assertEqual(FORWARDED_IP, values["ip_addr"])
|
||||
self.assertEqual("unavailable", values["remote_host"])
|
||||
self.assertEqual("test-agent/1.0", values["user_agent"])
|
||||
self.assertEqual(CLIENT_PORT, values["port"])
|
||||
@@ -127,7 +137,7 @@ class RoutesTest(unittest.TestCase):
|
||||
r = await client.get("/all.json", headers=ALL_HEADERS)
|
||||
self.assertEqual(200, r.status_code)
|
||||
data = json.loads(r.text)
|
||||
self.assertEqual(CLIENT_IP, data["ip_addr"])
|
||||
self.assertEqual(FORWARDED_IP, data["ip_addr"])
|
||||
self.assertEqual("test-agent/1.0", data["user_agent"])
|
||||
self.assertEqual(CLIENT_PORT, data["port"])
|
||||
self.assertEqual("GET", data["method"])
|
||||
|
||||
Reference in New Issue
Block a user