docs: add handoff for longpoll HTTP/2 truncation bug
The AuthSession cookie fix is shipped, but PouchDB longpoll requests get truncated at ~43s by Traefik on HTTP/2. HTTP/1.1 holds fine; CouchDB and nginx are clean. Captures the diagnosis trail and the three options for the next session so the work isn't re-derived.
This commit is contained in:
94
HANDOFF.md
Normal file
94
HANDOFF.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Handoff: PouchDB longpoll truncates at ~43s (Traefik HTTP/2)
|
||||
|
||||
## What's done — original cookie bug is fixed
|
||||
|
||||
The cross-origin `AuthSession` cookie bug from the previous handoff is **fully resolved**, deployed, and verified end-to-end.
|
||||
|
||||
- nginx sidecar fronts CouchDB (`nginx/Dockerfile`, `nginx/default.conf`).
|
||||
- `proxy_cookie_flags AuthSession secure;` adds `Secure` to the cookie that CouchDB itself refuses to mark (mochiweb only checks the raw socket scheme; `x_forwarded_proto`/`x_forwarded_ssl` are ignored for cookie attribution in CouchDB 3.5).
|
||||
- `couch_httpd_auth.same_site = none` is now persisted via `couchdb-init` (previously only set live; would vanish on container rebuild because `/opt/couchdb/etc/local.d/` is not in the data volume).
|
||||
- Coolify domain reassigned in UI from `couchdb` to `nginx:5984`. `couchdb` is now internal-only.
|
||||
|
||||
Verified externally:
|
||||
```
|
||||
set-cookie: AuthSession=...; HttpOnly; SameSite=None; Secure
|
||||
```
|
||||
|
||||
## New problem (the one to solve)
|
||||
|
||||
`_changes?feed=longpoll` requests from PouchDB receive a truncated response:
|
||||
```
|
||||
{"results":[
|
||||
<heartbeat newlines>
|
||||
```
|
||||
…then the connection is torn down before the closing `],"last_seq":"…"}`. Browser surfaces `SyntaxError: JSON.parse: unexpected end of data`. PouchDB can't process replication.
|
||||
|
||||
## Diagnosis (already done — don't repeat)
|
||||
|
||||
The cut happens at **~43s** and is **HTTP/2 specific**. Tested every segment of the chain:
|
||||
|
||||
| Path | Result |
|
||||
|---|---|
|
||||
| `curl → couchdb` directly (via `docker exec` on the couchdb container) | Holds indefinitely; ^C to stop |
|
||||
| `curl → nginx → couchdb` (via `docker run --network container:<nginx>`) | Holds indefinitely |
|
||||
| `curl HTTP/2 → Traefik → nginx → couchdb` | Dies at **43.77s** with `HTTP/2 stream … INTERNAL_ERROR (err 2)` |
|
||||
| `curl --http1.1 → Traefik → nginx → couchdb` | Held for **2:05+** before user ^C'd |
|
||||
|
||||
⇒ **Traefik is the source of the kill, and only for HTTP/2.** CouchDB and nginx are clean.
|
||||
|
||||
The reproducer (cookie is from `POST /_session` saved to `/tmp/c`):
|
||||
```bash
|
||||
time curl -v --cookie /tmp/c \
|
||||
'https://couch.apoena.dev/vaquant/_changes?feed=longpoll&since=now&heartbeat=10000&timeout=60000'
|
||||
```
|
||||
With `--http1.1` the same request is fine.
|
||||
|
||||
43s does not match any documented Traefik default. It's a Coolify-shipped or Go-stdlib HTTP/2 server timer. Related but not identical to the regression in [traefik/traefik#11986](https://github.com/traefik/traefik/issues/11986) — that one fires in milliseconds, not ~43s.
|
||||
|
||||
## What's NOT the cause (already ruled out)
|
||||
|
||||
- Not CouchDB's `chttpd.changes_timeout` or any CouchDB knob — direct connection holds.
|
||||
- Not nginx — `proxy_read_timeout 1h`, `proxy_buffering off`, and direct nginx→couchdb path holds.
|
||||
- Not the auth cookie — cookie is now valid through the whole chain.
|
||||
- Not a flaky network — 43s is consistent across runs.
|
||||
- Not the heartbeat — heartbeat newlines do flow through, they don't keep the H2 stream alive on Traefik's side.
|
||||
|
||||
## Decision pending (asked the user, no answer yet)
|
||||
|
||||
Three options on the table:
|
||||
|
||||
**A. Disable HTTP/2 on Coolify's HTTPS entrypoint** (recommended).
|
||||
Edit Coolify's Traefik static config, usually at `/data/coolify/proxy/traefik.conf` on the Coolify host. Add to the HTTPS entrypoint's TLS section, restrict ALPN to `http/1.1` only — or set `--entryPoints.https.http2.maxConcurrentStreams=0` or similar. Side effect: all HTTPS traffic on this Coolify server falls back to HTTP/1.1. For PouchDB's request pattern, this is fine; HTTP/2 multiplexing isn't load-bearing.
|
||||
|
||||
**B. Find the specific Traefik HTTP/2 timeout knob.**
|
||||
The 43s value is not in Traefik's documented defaults. Needs Coolify Traefik config inspection + trial. Higher effort, brittle.
|
||||
|
||||
**C. Live with it.**
|
||||
PouchDB does retry. Every ~43s of idle replication produces an error + reconnect. Functionally works, log noise + slight latency on resume of activity.
|
||||
|
||||
User leaned toward A but hasn't confirmed. Needs to know if they can edit Coolify's Traefik config.
|
||||
|
||||
## Repo layout (relevant files)
|
||||
|
||||
- `docker-compose.yml` — services: `couchdb` (internal), `nginx` (public via Traefik), `couchdb-init` (one-shot config), `signup` (Gleam service).
|
||||
- `nginx/Dockerfile` + `nginx/default.conf` — nginx sidecar that adds `Secure` cookie flag.
|
||||
- `signup/` — Gleam HTTP service for invite-gated user signup; `manifest.toml` is committed (locked dep tree). Uses wisp 2.x, mist 6.x.
|
||||
- `README.md` — explains the architecture (now including why nginx exists).
|
||||
- Coolify network UUID `lvw8efvnvsxduodrkg68zul3` is hardcoded in three places (two labels + one network alias); documented in README.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- The frontend/PouchDB app lives in a separate repo (`vaquant`). No changes needed there for the cookie fix; for option C (longpoll workaround at app level) the app would need adjusting.
|
||||
- CORS is correct, do not touch.
|
||||
- The signup service has its own deploy story (built from `./signup`), not relevant to this bug.
|
||||
|
||||
## Verify the cookie fix is still working (sanity check before touching anything)
|
||||
|
||||
```bash
|
||||
curl -i -X POST https://couch.apoena.dev/_session \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
-d "name=admin&password=$COUCHDB_PASSWORD" | grep -i set-cookie
|
||||
```
|
||||
Expect: `... HttpOnly; SameSite=None; Secure`.
|
||||
|
||||
If `Secure` is missing, the Coolify domain assignment slipped off `nginx` and is hitting `couchdb` directly — re-assign in the Domains tab.
|
||||
Reference in New Issue
Block a user