qr-reader: reply Connection: close (fixes ~10s beep delay)

The reader sends Connection: keep-alive but only acts on the verdict (beep,
output) once the TCP socket closes. Fastify's default kept the connection alive,
so the reader waited out a ~10s keep-alive timeout before beeping — even though
the server replied in ~15ms. Every vendor demo replies Connection: close and
shuts the socket. Set reply.header('connection','close') on the QR endpoint.

Verified the header is now sent; symptom was correct accept/reject with a ~10s
lag before the beep.
This commit is contained in:
2026-06-16 12:56:10 +02:00
parent 5705098054
commit ff3b011fe0
3 changed files with 25 additions and 1 deletions
+7 -1
View File
@@ -44,8 +44,14 @@ export async function qrReaderRoutes(
// No auth: the reader is a machine on the isolated device subnet and offers no // No auth: the reader is a machine on the isolated device subnet and offers no
// auth on its side. Public route, like the Dingtian input push. // auth on its side. Public route, like the Dingtian input push.
const handler = async (req: { query: ReaderQuery }) => { const handler = async (req: { query: ReaderQuery }, reply: import("fastify").FastifyReply) => {
const q = req.query; const q = req.query;
// The reader sends `Connection: keep-alive` but only ACTS on our verdict (beep,
// drive output) once the socket CLOSES — every vendor demo replies
// `Connection: close` and shuts the socket. Without it the reader waits out a
// ~10 s keep-alive timeout before beeping. So force-close the connection.
// See wiki/sources/qrcode-sdk.md, entities/gee-qr-er80.md.
reply.header("connection", "close");
const cardid = (q.cardid ?? "").trim(); const cardid = (q.cardid ?? "").trim();
const mjihao = q.mjihao != null ? Number(q.mjihao) : 0; const mjihao = q.mjihao != null ? Number(q.mjihao) : 0;
const serial = (q.cjihao ?? "").trim(); const serial = (q.cjihao ?? "").trim();
+9
View File
@@ -111,3 +111,12 @@ enter its serial here.
`status:1` 2-beep when the QR matches a permit/open session. `status:1` 2-beep when the QR matches a permit/open session.
- `output` is replied as `0` (Access). Confirm on hardware whether the reader needs `1`/`2` (WG26/34) - `output` is replied as `0` (Access). Confirm on hardware whether the reader needs `1`/`2` (WG26/34)
to drive its access line, vs. `0`. to drive its access line, vs. `0`.
## ⚠️ Reply MUST set `Connection: close` (verified on hardware)
The reader sends `Connection: keep-alive` but **only acts on the verdict (beep/output) once the TCP
socket CLOSES**. Fastify's default keeps the connection alive → the reader waits out a **~10 s
keep-alive timeout before beeping**, even though the server replied in ~15 ms. Every vendor demo
replies **`Connection: close`** and shuts the socket. Fix: the endpoint sets
`reply.header("connection","close")`. Symptom if regressed: correct accept/reject but a ~10 s lag
before the beep. (The request arrives fast; the delay is entirely the reader waiting for close.)
+9
View File
@@ -691,3 +691,12 @@ guarantee. Recorded in [[dingtian-relay]] (new Hardening section).
[[dingtian-relay]] connected. NOT for production. Registered in the catalog. [[dingtian-relay]] connected. NOT for production. Registered in the catalog.
- To get a live accept: assign Stub barrier to the reader's lane + a permit whose QR = the scanned - To get a live accept: assign Stub barrier to the reader's lane + a permit whose QR = the scanned
code → status:1 (2-beep) + logged pulseOpen. code → status:1 (2-beep) + logged pulseOpen.
## [2026-06-16] fix | QR reader 10s beep delay — reply must Connection: close
- Live accept worked (status:1, pulseOpen, 2 beeps) but the beep came ~10 s LATE. Server responded
in 14.7 ms; user confirmed request is fast, only the beep lags → delay is the READER, not us.
- Cause: reader sends `Connection: keep-alive` but only ACTS on the verdict once the socket CLOSES;
Fastify kept it alive → reader waited out a ~10 s keep-alive timeout. Every vendor demo replies
`Connection: close` + shuts the socket.
- Fix: endpoint sets `reply.header("connection","close")`. Verified the header is now sent.
- Updated [[gee-qr-er80]] (⚠️ Connection: close requirement).